Проблема 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; }

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


person Jesse Beder    schedule 14.08.2008    source источник
comment
Это происходит из-за двухэтапного поиска имени (который не все компиляторы используют по умолчанию). Есть 4 решения этой проблемы: 1) Используйте префикс A<T>::foo, 2) Используйте префикс this->foo, 3) Добавьте оператор using A<T>::foo, 4) Используйте глобальный переключатель компилятора, который включает разрешающий режим. Плюсы и минусы этих решений описаны в stackoverflow.com/questions/50321788/   -  person George Robinson    schedule 14.05.2018


Ответы (5)


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

person David Joyner    schedule 14.08.2008

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

Проблема при компиляции 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.

person Vincent Robert    schedule 14.08.2008
comment
С одной стороны, в этом есть смысл. Но с другой стороны, кажется, что это действительно отстой. Компилятору не нужно знать, к чему относится foo, пока не будет создан экземпляр шаблона, после чего он должен быть в состоянии распознать foo член в A. В C ++ слишком много таких странных случаев. - person Derek Park; 14.08.2008
comment
да, это совершенно неприемлемо ... не могу знать до создания экземпляра? затем дождитесь создания экземпляра, как обычно, в шаблонах .. в этом суть, не так ли? какой беспорядок ... - person neu-rah; 05.11.2014

Вот это да. 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>. ]

person Derek Park    schedule 14.08.2008

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

template<>
class A<int> {};

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

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

person hschober    schedule 17.10.2008