Problema GCC: uso de un miembro de una clase base que depende de un argumento de plantilla


El siguiente código no compila con gcc, pero sí con Visual Studio:

template <typename T> class A {
public:
    T foo;
};

template <typename T> class B: public A <T> {
public:
    void bar() { cout << foo << endl; }
};

Obtengo el error:

Prueba.cpp: En la función miembro'void B::bar ()':

Prueba.cpp: 11: error: 'foo' no fue declarado en este ámbito

¡Pero debería serlo! Si cambio bar a

void bar() { cout << this->foo << endl; }

Entonces hace compilar, pero no creo que tenga que hacer esto. ¿Hay algo en las especificaciones oficiales de C++ que GCC está siguiendo aquí, o es solo un ¿rareza?

Author: hmatar, 2008-08-14

5 answers

Esto cambió en gcc-3.4. El analizador sintáctico de C++ se volvió mucho más estricto en esa versión per según las especificaciones, pero sigue siendo un poco molesto para las personas con bases de código heredadas o multiplataforma.

 10
Author: David Joyner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-14 17:55:52

David Joyner tenía la historia, aquí está la razón.

El problema al compilar B<T> es que su clase base A<T> es desconocida por el compilador, siendo una clase de plantilla, por lo que no hay forma de que el compilador conozca a ningún miembro de la clase base.

Las versiones anteriores hicieron alguna inferencia al analizar realmente la clase de plantilla base, pero ISO C++ declaró que esta inferencia puede conducir a conflictos donde no debería haber.

La solución para hacer referencia a un miembro de la clase base en a template es usar this (como lo hiciste) o nombrar específicamente la clase base:

template <typename T> class A {
public:
    T foo;
};

template <typename T> class B: public A <T> {
public:
    void bar() { cout << A<T>::foo << endl; }
};

Más información en gcc manual.

 33
Author: Vincent Robert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-15 20:14:47

Wow. C++ nunca deja de sorprenderme con su rareza.

En una definición de plantilla, los nombres no calificados ya no encontrarán miembros de una base dependiente (como se especifica en [temp.dep] / 3 en el estándar C++). Por ejemplo,

template <typename T> struct B {
  int m;
  int n;
  int f ();
  int g ();
};
int n;
int g ();
template <typename T> struct C : B<T> {
  void h ()
  {
    m = 0; // error
    f ();  // error
    n = 0; // ::n is modified
    g ();  // ::g is called
  }
};

Debe hacer que los nombres dependan, por ejemplo, prefijándolos con this->. Aquí está la definición corregida de C::h,

template <typename T> void C<T>::h ()
{
  this->m = 0;
  this->f ();
  this->n = 0
  this->g ();
}

Como solución alternativa (desafortunadamente no compatible con versiones anteriores con GCC 3.3), puede usar usando declaraciones en lugar de esto ->:

template <typename T> struct C : B<T> {
  using B<T>::m;
  using B<T>::f;
  using B<T>::n;
  using B<T>::g;
  void h ()
  {
    m = 0;
    f ();
    n = 0;
    g ();
  }
};

Eso es todo tipo de locura. Gracias, David.

Aquí está la " temp.dep / 3" sección de la norma [ISO / IEC 14882:2003] a la que se refieren:

En la definición de una plantilla de clase o de 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 se examina durante la búsqueda de nombres no calificada en el punto de definición de la plantilla o miembro de clase o durante una instanciación de la plantilla o miembro de clase. [Ejemplo:

typedef double A; 
template<class T> class B { 
    typedef int A; 
}; 
template<class T> struct X : B<T> { 
    A a; // a has typedouble 
}; 

El nombre de tipo A en la definición de X<T> se vincula al nombre de tipo definido en el ámbito del espacio de nombres global, no al nombre de tipo definido en la clase base B<T>. ] [Ejemplo:

struct A { 
    struct B { /* ... */ }; 
    int a; 
    int Y; 
}; 
int a; 
template<class T> struct Y : T { 
    struct B { /* ... */ }; 
    B b; //The B defined in Y 
    void f(int i) { a = i; } // ::a 
    Y* p; // Y<T> 
}; 
Y<A> ya; 

Los miembros A::B, A::a, y A::Y del argumento de la plantilla A no afectan a la unión de nombres en Y<A>. ]

 18
Author: Derek Park,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-14 18:05:09

La razón principal por la que C++ no puede asumir nada aquí es que la plantilla base puede ser especializada para un tipo más adelante. Continuando con el ejemplo original:

template<>
class A<int> {};

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int>
 8
Author: Matt Price,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-14 21:06:41

VC no implementa la búsqueda de dos fases, mientras que GCC sí. Por lo tanto, GCC analiza las plantillas antes de que se creen instancias y, por lo tanto, encuentra más errores que VC. En su ejemplo, foo es un nombre dependiente, ya que depende de 'T'. A menos que le diga al compilador de dónde viene, no puede verificar la validez de la plantilla en absoluto, antes de instanciarla. Es por eso que tienes que decirle al compilador de dónde viene.

 3
Author: hschober,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-17 13:21:45