Проблема 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: error: 'foo не был объявлен в этой области

Но это должно быть! Если я изменю bar на

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

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

Ответ 1

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

Ответ 2

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

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

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

Решение ссылаться на элемент базового класса в шаблоне - это использовать 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.

Ответ 3

Ого. С++ никогда не перестает удивлять меня своей странностью.

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

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), вы можете использовать вместо него объявления, а именно: >

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>. ]

Ответ 4

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

template<>
class A<int> {};

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

Ответ 5

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