Зачем GCC нужны дополнительные объявления в шаблонах, а VS нет? - PullRequest
16 голосов
/ 11 мая 2010
template<typename T>
class Base
{
protected:
    Base() {}
    T& get() { return t; }
    T t;
};

template<typename T>
class Derived : public Base<T>
{
public:
    Base<T>::get;                    // Line A
    Base<T>::t;                      // Line B
    void foo() { t = 4; get(); }
};

int main() { return 0; }

Если я закомментирую строки A и B, этот код прекрасно компилируется в Visual Studio 2008. Тем не менее, когда я компилирую в GCC 4.1 с комментариями строк A и B, я получаю следующие ошибки:

В функции-члене void Derived :: foo () ’:
ошибка: «t» не было объявлено в этой области
ошибка: для «get» нет аргументов, которые зависят от параметра шаблона, поэтому должно быть доступно объявление «get»

Почему один компилятор требует строки A и B, а другой нет? Есть ли способ упростить это? Другими словами, если производные классы используют 20 вещей из базового класса, я должен поместить 20 строк объявлений для каждого класса, производного от Base! Есть ли способ обойти это, не требуя так много объявлений?

Ответы [ 2 ]

15 голосов
/ 11 мая 2010

GCC прав в этом случае, и Visual Studio по ошибке принимает неправильно сформированную программу. Взгляните на раздел Поиск имени в руководстве по GCC. Перефразируя:

[T] он вызывает [get()] не зависит от аргументов шаблона (нет аргументов, которые зависят от типа T, и также не указано, что вызов должен быть в [template-] зависимый контекст). Таким образом, глобальная декларация такой функции должна быть доступна, поскольку в базовом классе она не видна до времени ее создания.

Вы можете обойти это одним из трех способов:

  • Декларации, которые вы уже используете.
  • Base<T>::get()
  • this->get()

(Существует также четвертый путь, если вы хотите поддаться Темной стороне:

Использование флага -fpermissive также позволит компилятору принять код, отметив все вызовы функций, для которых не было видно ни одного объявления во время определения шаблона, для последующего поиска во время создания экземпляра, как если бы он был зависимым вызов. Мы не рекомендуем использовать -fpermissive для обхода недействительного кода, и он также будет отлавливать только случаи, когда вызываются функции в базовых классах, а не там, где используются переменные в базовых классах (как в примере выше).

Но я бы рекомендовал против этого как по причине, указанной в руководстве, так и по той причине, что ваш код все еще будет недействительным C ++.)

5 голосов
/ 11 мая 2010

Проблема связана не с gcc, а с Visual Studio, которая принимает код, который не соответствует стандарту C ++.

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

Стандарт требует, чтобы шаблоны оценивались дважды:

  • один раз в точке определения: template <class T> struct Foo { void bar(); };
  • один раз в точке экземпляра: Foo<int> myFoo;

В первый раз все независимые имена должны быть выведены из контекста:

  • компилятор сгенерирует, если вы забыли пунктуацию, ссылается на неизвестные типы / методы/ attribute
  • компилятор выберет перегрузку для функций, задействованных в этой точке

Поскольку синтаксис C ++ неоднозначен, необходимо помочь синтаксическому анализатору на этом этапе и использоватьtemplate и typename ключевые слова для устранения неоднозначности вручную.

К сожалению, Visual Studio не соответствует требованиям и реализует только вторую оценку (в момент возникновения).ион).Преимущество для ленивых в том, что вы можете обойтись без лишних ключевых слов template и typename, недостаток в том, что ваш код плохо сформирован и не переносим ...

Теперь самое интересное:

void foo(int) { std::cout << "int" << std::endl; }

template <class T> void tfoo(T i) { foo(i); }

void foo(double) { std::cout << "double" << std::endl; }

int main(int argc, char* argv[])
{
  double myDouble = 0.0;
  tfoo(myDouble);
  return 0;
}

Скомпилированный с gcc, он выводит int.

Скомпилированный с Visual Studio, он выводит double.

Проблема?Любой, кто повторно использует тот же символ, который вы используете в коде шаблона в VS, может натолкнуться на вашу реализацию, если его символ появится между включением кода шаблона и моментом, когда он фактически использует код шаблона ... не так ли?смешно: /?

Теперь для вашего кода:

template<typename T>
class Derived : public Base<T>
{
public:
  void foo() { this->t = 4; this->get(); }
};

this указывает, что следующее имя является зависимым именем , то есть оно зависит от T (что не очевидно, когда символ появляется один).Поэтому компилятор будет ожидать инстанцирования и посмотреть, содержит ли инстанцированный вами шаблон конкретный тип Base<T> эти методы.Это не обязательно, так как я мог бы прекрасно специализировать Base:

// It's non-sensical to instanciate a void value,
template <>
class Base<void> {};

И, следовательно, Derived<void> не должен компилироваться;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...