2009-08-06 17 views
40

considerar:¿Por qué una clase de plantilla derivada no tiene acceso a los identificadores de una clase de plantilla 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(); 
} 

no soy capaz de compilación con GCC este g ++ 3.4.4 (cygwin).

Antes de convertirlos en plantillas de clase, no eran genéricos y la clase derivada podía ver los miembros estáticos de la clase base. ¿Es esta pérdida de visibilidad un requisito de la especificación C++ o hay un cambio de sintaxis que necesito emplear?

que entender que cada instancia de Base<T> tendrá su propio miembro estático "ZEROFILL" y "NO_ZEROFILL", que Base<float>::ZEROFILL y Base<double>::ZEROFILL son diferentes variables, pero no me importa; la constante está ahí para la lectura del código. Quería usar una constante estática porque es más seguro en términos de conflictos de nombres que de macro o global.

Respuesta

43

Eso es de búsqueda de dos fases para usted.

Base<T>::NO_ZEROFILL (todos los identificadores de mayúsculas son boo, excepto por las macros, BTW) es un identificador que depende de T.
Dado que, cuando el compilador primero analiza la plantilla, no hay ningún tipo real sustituido por T, sin embargo, el compilador no "sabe" qué es Base<T>. Por lo tanto, no puede conocer ningún identificador que suponga definido en él (puede haber una especialización para algunos T s que el compilador solo ve más adelante) y no puede omitir la calificación de la clase base de los identificadores definidos en la clase base. Por favor, escriba Base<T>::NO_ZEROFILL (o this->NO_ZEROFILL). Eso le dice al compilador que NO_ZEROFILL es algo en la clase base, que depende de T, y que solo puede verificarlo más tarde, cuando se crea una instancia de la plantilla. Por lo tanto, lo aceptará sin intentar verificar el código.
Ese código solo se puede verificar más adelante, cuando se crea una instancia de la plantilla proporcionando un parámetro real para T.

+1

aw, me ganaste por 20 segundos. +1 – jalf

+1

@jalf: Entonces, por una vez, soy el primero. ': ^>' Siéntete libre de mejorarlo. – sbi

+1

Interesante. ¿Los miembros heredados no estáticos requieren la misma calificación? es decir, Base :: memberFunction() – cheshirekow

1

parece compilar bien en contra de 2008. Ha intentado:

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

En realidad, VC no realiza la búsqueda en dos fases. Es por eso que compila allí. Y es por eso que es una mala idea crear una lib de plantilla usando VC: tendrás que arreglar muchas cosas cuando lo necesites en cualquier otro compilador. – sbi

+0

las correcciones son generalmente bastante triviales sin embargo. Se trata principalmente de insertar una gran cantidad de 'typename''s, y arreglar la búsqueda ocasional de 2 fases. – jalf

+3

@jalf: Es cierto, excepto donde no lo es. Entre otros, me encontré con problemas de interdependencia muy desagradables que no se descubrieron con VC porque VC solo analizaría realmente la plantilla cuando se creó una instancia, y para entonces todas las entidades dependientes estaban dentro del alcance. Durante el primer análisis en una búsqueda de dos fases adecuada, esto se hizo pedazos y tomó bastante tiempo y una aspersión liberal de la cura universal del programador (indirección) para desenredar el desastre. El código fue mucho más difícil de entender después de eso, y probablemente se habría diseñado de manera diferente si el problema se hubiera conocido antes. – sbi

28

El problema que ha encontrado se debe a las reglas de búsqueda de nombres para las clases base dependientes. 14,6/8 tiene:

Al buscar la declaración de un nombre que se utiliza en una definición de plantilla, las reglas de búsqueda habituales (3.4.1, 3.4.2) se utilizan para los nombres no dependientes. La búsqueda de nombres que dependen de los parámetros de la plantilla es pospuesta hasta conocer el argumento de la plantilla real (14.6.2).

(Esto no es realmente "búsqueda de 2 fases." - ver más abajo para una explicación de eso)

El punto de aproximadamente 14,6/8 es que por lo que el compilador se refiere NO_ZEROFILL en su ejemplo es un identificador y no depende del parámetro de la plantilla. Por lo tanto, se busca según las reglas normales de 3.4.1 y 3.4.2.

Esta búsqueda normal no busca dentro de Base<T>, por lo que NO_ZEROFILL es simplemente un identificador no declarado. 14.6.2/3 tiene:

En la definición de una plantilla de clase o un miembro de una plantilla de clase, si una clase base de la plantilla de clase depende de un parámetro de plantilla, el ámbito de la clase base no está examinados durante búsqueda de nombre no calificado bien en el punto de definición de la plantilla de clase o miembro o durante una instanciación de la plantilla de clase o miembro.

Cuando se califica NO_ZEROFILL con Base<T>::, en esencia, que están cambiando de ser un nombre no depende en un dependiente y cuando se hace eso se demora hasta su lookup se crea una instancia de la plantilla.

Nota al margen: ¿Qué es la consulta de 2 fases:

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); 
} 

El ejemplo anterior se compila la siguiente manera. El compilador analiza el cuerpo de la función foo y ve que hay una llamada al bar que tiene un argumento dependiente (es decir, uno que depende del parámetro de la plantilla). En este punto, el compilador busca la barra según 3.4.1 y esta es la "búsqueda de fase 1". La búsqueda encontrará la función void bar (int) y se almacena con la llamada dependiente hasta más adelante.

Cuando se crea una instancia de la plantilla (como resultado de la llamada de main), el compilador realiza una búsqueda adicional en el alcance del argumento, esta es la "búsqueda de fase 2". Este caso que resulta en encontrar void NS::bar(A const &).

El compilador tiene dos sobrecargas para bar y selecciona entre ellas, en el caso anterior llamando al void NS::bar(A const &).

+4

+1, buena explicación :) Lástima que pareces haber dejado de responder en stackoverflow. Me encantaron tus discusiones elaboradas e intensivas. –

+0

¡Es bueno extrañarse! Espero volver a responder preguntas en el futuro cercano. –

+0

@RichardCorden No entiendo completamente su nota al margen para ilustrar la "búsqueda en 2 fases". Según entiendo, el nombre de la función 'bar' en el cuerpo de la función de' foo' es un nombre dependiente, porque su parámetro depende del tipo. ¿No debería posponerse la búsqueda de nombre de 'bar' en' foo' hasta que se conozca el argumento de la plantilla real? En realidad, clang ++ 3.8.1 no produce ningún error sobre la búsqueda de nombres, incluso si hago un comentario sobre la línea 'void bar (int);'. Así que dudo que la "búsqueda de fase 1" para 'bar' realmente se realice. – Carousel

0

probar este programa

#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; 
} 

en la línea de T get(void){return this->x+2;} u también puede utilizar la resolución de ámbito (: :) operador. por ejemplo, intente reemplazar la línea con

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

Formatee correctamente su código. Explicar por qué funciona el código y dónde está el problema en el código original, por lo general también es algo bueno. – stefan

Cuestiones relacionadas