Почему GCC нуждается в дополнительных объявлениях в шаблонах, когда VS нет?
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():
error: 't не был объявлен в этой области
error: нет аргументов, чтобы "получить, что зависит от параметра шаблона, поэтому объявление" get должно быть доступно "
Почему одному компилятору нужны строки A и B, а другой нет? Есть ли способ упростить это? Другими словами, если производные классы используют 20 вещей из базового класса, я должен поместить 20 строк деклараций для каждого класса, полученного из Base! Есть ли способ вокруг этого, который не требует так много объявлений?
Ответы
Ответ 1
GCC прав в этом случае, и Visual Studio ошибочно принимает неверную форму. Посмотрите раздел Поиск имени в руководстве GCC. Перефразируя:
[T] он вызывает [ get()
] не зависит от аргументов шаблона (нет аргументов, которые зависят от типа T, а также не указано иначе, что вызов должен быть в [template-] зависимый контекст). Таким образом, глобальное объявление такой функции должно быть доступно, поскольку одно в базовом классе не отображается до момента создания экземпляра.
Вы можете обойти это одним из трех способов:
- Объявления, которые вы уже используете.
-
Base<T>::get()
-
this->get()
(Существует также четвертый способ, если вы хотите поддаться Темной стороне:
Использование флага -fpermissive
также позволит компилятору принять код, пометив все вызовы функций, для которых объявление не отображается во время определения шаблона для последующего поиска во время создания экземпляра, как если бы оно было зависимым вызов. Мы не рекомендуем использовать -fpermissive
для работы с недопустимым кодом, и он также будет улавливать случаи, когда вызываются функции в базовых классах, а не где используются переменные в базовых классах (как в примере выше).
Но я бы рекомендовал против этого, как по причине, упомянутой в руководстве, так и по той причине, что ваш код по-прежнему будет недействительным С++.)
Ответ 2
Проблема не в gcc, а в Visual Studio, которая принимает код, который не соответствует стандарту С++.
На этом сайте был дан ответ, поэтому я буду кратким.
Стандарт требует, чтобы шаблоны дважды оценивались:
- один раз в точке определения:
template <class T> struct Foo { void bar(); };
- один раз в точке instanciation:
Foo<int> myFoo;
В первый раз все независимые имена будут вычитаться из контекста:
- компилятор выкинет, если вы забыли пунктуацию, ссылаетесь на неизвестные типы/методы/атрибуты
- компилятор выберет перегрузку для функций, задействованных в этой точке
Поскольку синтаксис С++ неоднозначен, необходимо помочь синтаксическому анализатору на этом этапе и использовать ключевые слова template
и typename
, чтобы правильно устранить проблему.
К сожалению, Visual Studio не соответствует требованиям и выполняет только вторую оценку (в момент instanciation). Преимущество для ленивых заключается в том, что вы можете уйти без этих дополнительных 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
(что не очевидно, когда символ появляется в одиночку). Поэтому компилятор ждет instanciation и посмотрит, не содержит ли тот или иной тип экземпляр шаблона Base<T>
эти методы. Это не обязательно, так как я мог бы отлично специализироваться Base
:
// It non-sensical to instanciate a void value,
template <>
class Base<void> {};
И, таким образом, Derived<void>
не следует компилировать;)