Perché GCC ha bisogno di dichiarazioni extra nei modelli quando VS non lo fa?

template class Base { protected: Base() {} T& get() { return t; } T t; }; template class Derived : public Base { public: Base::get; // Line A Base::t; // Line B void foo() { t = 4; get(); } }; int main() { return 0; } 

Se commento le righe A e B, questo codice viene compilato correttamente in Visual Studio 2008. Tuttavia quando compilo sotto GCC 4.1 con le righe A e B commentate, ottengo questi errori:

Nella funzione membro ‘void Derived :: foo ()’:
errore: ‘t’ non è stato dichiarato in questo ambito
errore: non ci sono argomenti per ‘ottenere’ che dipendono da un parametro del modello, quindi deve essere disponibile una dichiarazione di ‘get’

Perché un compilatore dovrebbe richiedere le linee A e B mentre l’altro no? C’è un modo per semplificare questo? In altre parole, se le classi derivate usano 20 cose dalla class base, devo mettere 20 righe di dichiarazioni per ogni class derivante da Base! C’è un modo per aggirare questo che non richiede così tante dichiarazioni?

GCC ha ragione in questo caso e Visual Studio accetta erroneamente un programma malformato. Dai un’occhiata alla sezione sulla ricerca dei nomi nel manuale GCC. Parafrasando:

[T] la chiamata a [ get() ] non dipende dagli argomenti del template (non ci sono argomenti che dipendono dal tipo T, e inoltre non è specificato diversamente che la chiamata debba essere in un contesto [template-] dipendente) . Quindi una dichiarazione globale di tale funzione deve essere disponibile, poiché quella nella class base non è visibile fino al tempo di istanziazione.

Puoi aggirare questo in tre modi:

  • Le dichiarazioni che stai già utilizzando.
  • Base::get()
  • this->get()

(C’è anche una quarta via, se vuoi soccombere al Lato Oscuro:

L’uso del flag -fpermissive consentirà inoltre al compilatore di accettare il codice, contrassegnando tutte le chiamate di funzione per le quali non è visibile alcuna dichiarazione al momento della definizione del modello per una successiva ricerca al momento dell’istanziazione, come se si trattasse di una chiamata dipendente. Non consigliamo l’uso di -fpermissive per aggirare il codice non valido, e catturerà anche i casi in cui vengono chiamate le funzioni nelle classi base, non dove vengono utilizzate le variabili nelle classi base (come nell’esempio sopra).

Ma mi sconsiglio di farlo, sia per la ragione menzionata nel manuale, sia per il motivo che il tuo codice sarà comunque C ++ invalido.)

Il problema non è con gcc ma con Visual Studio, che accetta codice non conforms allo standard C ++.

È stato risposto in tutto e per tutto proprio in questo sito, quindi sarò breve.

Lo standard richiede che i modelli vengano valutati due volte:

  • una volta al punto di definizione: template struct Foo { void bar(); }; template struct Foo { void bar(); };
  • una volta sul punto di instanciazione: Foo myFoo;

La prima volta, tutti i nomi non dipendenti sono deducibili dal contesto:

  • il compilatore si solleva se hai dimenticato la punteggiatura, fa riferimento a tipi / metodi / attributi sconosciuti
  • il compilatore selezionerà il sovraccarico per le funzioni coinvolte a questo punto

Poiché la syntax C ++ è ambigua, è necessario aiutare il parser in questa fase e utilizzare le parole chiave template e typename appropriato per disambiguare manualmente.

Sfortunatamente, Visual Studio non è conforms e implementa solo la seconda valutazione (al momento dell’introduzione). Il vantaggio per i pigri è che puoi scappare senza quelle parole chiave extra di template e typename , lo svantaggio è che il tuo codice è mal formato e non portatile …

Adesso per la parte divertente:

 void foo(int) { std::cout << "int" << std::endl; } template  void tfoo(T i) { foo(i); } void foo(double) { std::cout << "double" << std::endl; } int main(int argc, char* argv[]) { double myDouble = 0.0; tfoo(myDouble); return 0; } 

Compilato con gcc, emette int .

Compilato con Visual Studio, emette double .

Il problema ? Chiunque riutilizzi lo stesso simbolo che fai nel tuo codice template in VS potrebbe fare un passo falso alla tua implementazione se il loro simbolo appare tra l'inclusione del tuo codice template e il momento in cui effettivamente usano il codice template ... non è vero? divertente :/ ?

Ora, per il tuo codice:

 template class Derived : public Base { public: void foo() { this->t = 4; this->get(); } }; 

this indica che il nome che segue è un nome dipendente , cioè dipende da T (che non è ovvio quando il simbolo appare da solo). Il compilatore attenderà quindi l'instanciazione e vedrà se per il particolare tipo che instanziate il template Base contiene quei metodi. Non è obbligatorio, dal momento che ho potuto perfettamente specializzare Base :

 // It's non-sensical to instanciate a void value, template <> class Base {}; 

E così Derived non dovrebbe essere compilato;)