2009-08-06 17 views
40

Considerare:Perché una classe modello derivata non ha accesso agli identificatori di una classe modello di base?

template <typename T> 
class Base 
{ 
    public: 
     static const bool ZEROFILL = true; 
     static const bool NO_ZEROFILL = false; 
} 

template <typename T> 
class Derived : public Base<T> 
{ 
    public: 
     Derived(bool initZero = NO_ZEROFILL); // NO_ZEROFILL is not visible 
     ~Derived(); 
} 

Io non sono in grado di compilazione con GCC questo g ++ 3.4.4 (Cygwin).

Prima di convertirli in modelli di classe, non erano generici e la classe derivata era in grado di vedere i membri statici della classe base. È questa perdita di visibilità in un requisito delle specifiche C++ o c'è una modifica della sintassi che devo impiegare?

Capisco che ogni istanza di Base<T> avrà un proprio membro statico "ZEROFILL" e "NO_ZEROFILL", che Base<float>::ZEROFILL e Base<double>::ZEROFILL sono diverse variabili, ma io non mi interessa; la costante è lì per la leggibilità del codice. Volevo utilizzare una costante statica perché è più sicura in termini di conflitti tra nomi piuttosto che macro o globali.

risposta

43

Ecco ricerca in due fasi per voi.

Base<T>::NO_ZEROFILL (gli identificatori di maiuscole sono boo, ad eccezione di macro, BTW) è un identificatore che dipende da T.
Poiché, quando il compilatore analizza per la prima volta il modello, non esiste ancora un tipo reale sostituito per T, il compilatore non "sa" cosa è Base<T>. Quindi non può conoscere alcun identificatore che si assume essere definito in esso (potrebbe esserci una specializzazione per alcuni T s che il compilatore vede solo in seguito) e non è possibile omettere la qualifica della classe base dagli identificatori definiti nella classe base.

Ecco perché è necessario scrivere Base<T>::NO_ZEROFILL (o this->NO_ZEROFILL). Questo dice al compilatore che NO_ZEROFILL è qualcosa nella classe base, che dipende da T e che può solo verificarlo in seguito, quando il modello viene istanziato. Lo accetterà quindi senza provare a verificare il codice.
Questo codice può essere verificato solo in seguito, quando il modello viene istanziato fornendo un parametro effettivo per T.

+1

aw, mi hai battuto per 20 secondi. +1 – jalf

+1

@jalf: Quindi per una volta, sono il primo. ': ^>' Sentitevi liberi di migliorare. – sbi

+1

Interessante. I membri non statici ereditati richiedono quindi la stessa qualifica? cioè Base :: memberFunction() – cheshirekow

1

sembra compilare ok in vs 2008. Hai provato:

public: 
    Derived(bool initZero = Base<T>::NO_ZEROFILL); 
+5

In realtà, VC non esegue la ricerca in due fasi. Ecco perché compila lì. Ed è per questo che non è una buona idea creare una lib di template usando VC - avrai un sacco di cose da sistemare quando ne hai bisogno su qualsiasi altro compilatore. – sbi

+0

tuttavia le correzioni sono generalmente abbastanza banali. Si tratta principalmente di inserire un sacco di 'typename' e di correggere occasionalmente la ricerca in 2 fasi. – jalf

+3

@jalf: Questo è vero tranne dove non lo è. Tra l'altro, mi sono imbattuto in problemi di interdipendenza molto spiacevoli che non sono stati scoperti con VC perché VC avrebbe solo analizzato il modello quando è stato istanziato - e quindi tutte le entità dipendenti erano in ambito. Durante il primo parsing in una corretta ricerca in due fasi, questo è caduto in pezzi e ci è voluto un po 'di tempo e una spolverata liberale della cura universale del programmatore (indiretta) per districare il caos. Dopo questo, il codice era molto più difficile da capire e probabilmente sarebbe stato progettato diversamente se il problema fosse stato conosciuto prima. – sbi

28

Il problema riscontrato è dovuto al nome delle regole di ricerca per le classi di base dipendenti. 14,6/8 ha:

Quando si cerca per la dichiarazione di un nome usato in una definizione di modello, le regole di ricerca abituali (3.4.1, 3.4.2 ) vengono utilizzati per i nomi nondependent. La ricerca dei nomi che dipendono dai parametri del modello è posticipata fino a quando non si conosce l'argomento del modello effettivo (14.6.2).

(Questo non è proprio "2 fasi di ricerca." - vedi sotto per una spiegazione di quello)

Il punto circa 14,6/8 è che per quanto riguarda il compilatore è interessato NO_ZEROFILL nel tuo esempio è un identificatore e non dipende dal parametro del modello. Viene quindi esaminato secondo le normali regole di 3.4.1 e 3.4.2.

Questa ricerca normale non esegue la ricerca all'interno di Base<T> e pertanto NO_ZEROFILL è semplicemente un identificatore non dichiarato. 14.6.2/3 ha:

Nella definizione di un modello di classe o un membro di una classe template, se una classe di base del modello classe dipende da un modello di parametri, nell'ambito di classe base non è esaminata durante ricerca nome non qualificata o al punto di definizione del modello di classe o membro o durante un'istanza del modello di classe o membro.

Quando si qualificano NO_ZEROFILL con Base<T>:: in sostanza si sta cambiando da essere un nome non dipende in una dipendente e quando lo fai si ritarda la sua ricerca fino a quando il modello viene creata un'istanza.

Nota a margine: Qual è lookup 2 fasi:

void bar (int); 

template <typename T> 
void foo (T const & t) { 
    bar (t); 
} 


namespace NS 
{ 
    struct A {}; 
    void bar (A const &); 
} 


int main() 
{ 
    NS::A a; 
    foo (a); 
} 

L'esempio precedente è stato compilato come segue. Il compilatore analizza il corpo della funzione di foo e vede che c'è una chiamata a bar che ha un argomento dipendente (cioè uno che dipende dal parametro del modello). A questo punto il compilatore cerca la barra come in 3.4.1 e questa è la "ricerca di fase 1". La ricerca troverà la funzione void bar (int) e verrà memorizzata con la chiamata dipendente fino a un momento successivo.

Quando il modello viene quindi istanziato (come risultato della chiamata da main), il compilatore esegue quindi una ricerca aggiuntiva nell'ambito dell'argomento, questa è la "ricerca di fase 2". Questo caso che risulta nella ricerca di void NS::bar(A const &).

Il compilatore ha due sovraccarichi per bar e seleziona tra di essi, nel caso precedente chiamando void NS::bar(A const &).

+4

+1, bella spiegazione :) Peccato che tu abbia smesso di rispondere su StackOverflow. Ho adorato le tue discussioni elaborative e intensive. –

+0

È bello da non perdere! Spero di tornare a rispondere alle domande nel prossimo futuro. –

+0

@RichardCorden Non capisco completamente le note a margine per illustrare la "ricerca in 2 fasi". Come ben inteso, il nome della funzione 'bar' nel corpo della funzione di' foo' è un nome dipendente, poiché il suo parametro è dipendente dal tipo. La ricerca del nome di 'bar' in' pippo 'non dovrebbe essere posticipata fino a quando non si conosce l'effettivo argomento del template? In realtà, clang ++ 3.8.1 non produce alcun errore sulla ricerca del nome anche se commento la riga 'void bar (int);'. Quindi dubito che la "ricerca di fase 1" per 'bar' sia effettivamente eseguita. – Carousel

0

provare questo programma

#include<iostream> 
using namespace std; 
template <class T> class base{ 
public: 
T x; 
base(T a){x=a;} 
virtual T get(void){return x;} 
}; 
template <class T> 
class derived:public base<T>{ 
public: 
derived(T a):base<T>(a){} 
T get(void){return this->x+2;} 
}; 
int main(void){ 
base<int> ob1(10); 
cout<<ob1.get()<<endl; 
derived<float> ob(10); 
cout<<ob.get(); 
return 0; 
} 

nella linea T get(void){return this->x+2;} u può anche utilizzare la risoluzione ambito (: :) dell'operatore. ad esempio, prova a sostituire la linea con

T get(void){return base<T>::x+2;} 
+1

Si prega di formattare correttamente il codice. Spiegazione sul perché il codice funziona e dove si trova il problema nel codice originale, di solito è anche una buona cosa. – stefan

Problemi correlati