2010-05-11 25 views
16
template<typename T> 
class Base 
{ 
protected: 
    Base() {} 
    T& get() { return t; } 
    T t; 
}; 

template<typename T> 
class Derived : public Base<T> 
{ 
public: 
    Base<T>::get;     // Line A 
    Base<T>::t;      // Line B 
    void foo() { t = 4; get(); } 
}; 

int main() { return 0; } 

Si comento hacia fuera las líneas A y B, este código compila bien en Visual Studio 2008. Sin embargo, cuando compilo bajo GCC 4.1 con las líneas A y B comentaron, consigo estos errores:¿Por qué GCC necesita declaraciones adicionales en las plantillas cuando VS no lo hace?

In member function ‘void Derived::foo()’:
error: ‘t’ was not declared in this scope
error: there are no arguments to ‘get’ that depend on a template parameter, so a declaration of ‘get’ must be available

Por qué ¿un compilador requeriría las líneas A y B mientras que el otro no? ¿Hay alguna forma de simplificar esto? En otras palabras, si las clases derivadas usan 20 elementos de la clase base, ¡tengo que poner 20 líneas de declaraciones para cada clase derivada de Base! ¿Hay alguna forma de evitar esto que no requiera tantas declaraciones?

+6

Un vínculo obligatorio a C++ FAQ: http://www.parashift.com/c++-faq- lite/templates.html # faq-35.19 – UncleBens

+4

Respuesta corta: porque gcc cumple con los estándares y (sorprendentemente) con Visual C++ no? –

Respuesta

15

GCC es justo en este caso, y Visual Studio acepta erróneamente un programa mal formado. Eche un vistazo a la sección en Name lookup en el manual de GCC. Parafraseando:

[T]he call to [ get() ] is not dependent on template arguments (there are no arguments that depend on the type T, and it is also not otherwise specified that the call should be in a [template-]dependent context). Thus a global declaration of such a function must be available, since the one in the base class is not visible until instantiation time.

Usted puede evitar esto en cualquiera de tres maneras:

  • Las declaraciones que ya se está utilizando.
  • Base<T>::get()
  • this->get()

(También hay una cuarta forma, si quieres sucumbir al lado oscuro:

Using the -fpermissive flag will also let the compiler accept the code, by marking all function calls for which no declaration is visible at the time of definition of the template for later lookup at instantiation time, as if it were a dependent call. We do not recommend using -fpermissive to work around invalid code, and it will also only catch cases where functions in base classes are called, not where variables in base classes are used (as in the example above).

pero recomendaría contra de eso, tanto por la razón mencionado en el manual, y por la razón de que su código aún no será válido C++.)

+0

"erróneamente" es incorrecto. Si deshabilita extensiones no estándar en Visual C++ (lo que debe hacer, si desea usarlo para escribir código portátil), se le advertirá que su código utiliza esta extensión. –

+1

No, estas extensiones son mejores que el "estándar" deslucido. – HardCoder

5

El problema no es con gcc sino con Visual Studio, w Que acepta código que no se ajusta al estándar C++.

Ha sido respondida hasta el final en este mismo sitio, así que seré breve.

La norma requiere plantillas para ser evaluado dos veces:

  • una vez en el punto de definición: template <class T> struct Foo { void bar(); };
  • una vez en el punto de instanciation: Foo<int> myFoo;

La primera vez, todo el los nombres no dependientes serán deducibles del contexto:

  • el compilador vomitar si olvidó puntuacion, se refiere a los tipos/métodos desconocidos/atributos
  • el compilador seleccionará la sobrecarga de las funciones involucradas en este punto

Debido sintaxis de C++ es ambigua, es necesario para ayudar al analizador en esta fase y utilizar las palabras clave template y typename de forma adecuada para desambiguar manualmente.

Desafortunadamente, Visual Studio no cumple, y solo implementa la segunda evaluación (en el momento de la instanciación).La ventaja para los perezosos es que se puede escapar sin esos extras template y typename palabras clave, la desventaja es que su código está mal formado y no es portátil ...

Ahora viene la parte divertida:

void foo(int) { std::cout << "int" << std::endl; } 

template <class T> 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; 
} 

Compilado con gcc, emite int.

Compilado con Visual Studio, emite double.

Problema? Cualquier persona que reutilice el mismo símbolo que usa en su código de plantilla en VS podría pisarle el pie y estropear su implementación si su símbolo aparece entre la inclusión de su código de plantilla y el momento en que realmente usa el código de la plantilla ... ¿no es así? gracioso :/ ?

Ahora, para su código:

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

El this indica que el nombre que sigue es un nombre dependiente, es decir, que depende de T (que no es evidente cuando el símbolo aparece solo). El compilador, por lo tanto, esperará la instanciación y verá si para el tipo particular que instancia la plantilla Base<T> contiene esos métodos. No es obligatorio, ya que podía perfectamente especializarse Base:

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

Y así Derived<void> no debería compilar;)

Cuestiones relacionadas