Проблема 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 следует здесь, или это просто странность?

14.08.2008 17:39:46
Это происходит из-за двухфазного поиска имени (который не все компиляторы используют по умолчанию). Существует 4 варианта решения этой проблемы: 1) Использовать префикс A<T>::foo, 2) Использовать префикс this->foo, 3) Добавить оператор using A<T>::foo, 4) Использовать глобальный переключатель компилятора, который включает разрешающий режим. Плюсы и минусы этих решений описаны в stackoverflow.com/questions/50321788/…
George Robinson 14.05.2018 13:37:51
5 ОТВЕТОВ
РЕШЕНИЕ

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

12
14.08.2008 17:55:52

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

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

19
14.08.2008 18:05:09

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

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

34
15.12.2015 20:14:47
С одной стороны, это имеет смысл. Но с другой стороны, это кажется действительно хромым. Компилятору не нужно знать, на что fooссылается, пока не будет создан экземпляр шаблона, и в этот момент он сможет распознать fooчлен A. В C ++ слишком много таких странных угловых случаев.
Derek Park 14.08.2008 18:21:01
да, это совершенно неприемлемо ... не могу знать, прежде чем создавать экземпляр? затем, как обычно, дождитесь инстанцирования в шаблонах ... в этом дух? какой беспорядок ...
neu-rah 5.11.2014 13:19:22

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

template<>
class A<int> {};

B<int> x; 
x.bar();//this will fail because there is no member foo in A<int>
8
14.08.2008 21:06:41

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

3
17.10.2008 13:21:45