Проблема GCC: использование члена базового класса, который зависит от аргумента шаблона

Следующий код компилируется не с gcc, а с Visual Studio:

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

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

Я получаю сообщение об ошибке:

test.cpp: в функции-члене void B :: bar ():

test.cpp: 11: ошибка: 'foo' не был объявлен в этой области

Но так и должно быть! Если я перейду bar на

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

то это делает компиляции, но я не думаю , что я должен сделать это. Есть ли что-то в официальных спецификациях C++, которым здесь следует GCC, или это просто причуда?

Ответов (5)

Решение

Это изменилось в gcc-3.4 . В этом выпуске синтаксический анализатор C++ стал намного более строгим - согласно спецификации, но по-прежнему раздражает людей с устаревшей или многоплатформенной кодовой базой.

Основная причина, по которой C++ не может здесь ничего предполагать, заключается в том, что базовый шаблон может быть специализирован для определенного типа позже. Продолжая исходный пример:

template<>
class A<int> {};

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int>

Вот это да. C++ не перестает меня удивлять своей странностью.

В определении шаблона неквалифицированные имена больше не будут находить элементы зависимой базы (как указано в [temp.dep] / 3 в стандарте C++). Например,

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

Вы должны сделать имена зависимыми, например, поставив перед ними префикс this->. Вот исправленное определение C :: h,

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

В качестве альтернативного решения (к сожалению, не имеющего обратной совместимости с GCC 3.3) вы можете использовать объявления using вместо this->:

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

Это просто безумие всякого рода. Спасибо, Дэвид.

Вот раздел «temp.dep / 3» стандарта [ISO / IEC 14882: 2003], на который они ссылаются:

В определении шаблона класса или члена шаблона класса, если базовый класс шаблона класса зависит от параметра-шаблона, область действия базового класса не проверяется во время поиска неквалифицированного имени либо в точке определения класса шаблон или член, или во время создания экземпляра шаблона или члена класса. [Пример:

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

Имя типа Aв определении X<T>связывается с именем typedef, определенным в области глобального пространства имен, а не с именем typedef, определенным в базовом классе B<T>. ] [Пример:

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; 

Члены A::B, A::aи A::Yаргумента шаблона Aне влияют на привязку имен в Y<A>. ]

У Дэвида Джойнера была история, вот почему.

Проблема при компиляции B<T> заключается в том, что его базовый класс A<T> неизвестен компилятору, поскольку является классом шаблона, поэтому компилятор не может узнать какие-либо члены из базового класса.

В более ранних версиях был сделан некоторый вывод, фактически анализируя базовый класс шаблона, но ISO C++ заявил, что этот вывод может привести к конфликтам там, где их не должно быть.

Решение для ссылки на член базового класса в шаблоне состоит в том, чтобы использовать this (как и вы) или конкретно назвать базовый класс:

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

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

Дополнительная информация в руководстве по gcc .

В VC не реализован двухэтапный поиск, в отличие от GCC. Таким образом, GCC анализирует шаблоны до их создания и, таким образом, находит больше ошибок, чем VC. В вашем примере foo - это зависимое имя, поскольку оно зависит от 'T'. Если вы не сообщите компилятору, откуда он взялся, он вообще не сможет проверить действительность шаблона, прежде чем вы создадите его экземпляр. Вот почему вы должны сообщить компилятору, откуда оно взялось.