Почему мне нужно использовать typedef typename в g++, но не VS?
Прошло много времени с тех пор, как GCC поймал меня с этим, но это случилось только сегодня. Но я никогда не понимал, почему GCC требует typedef typename в шаблонах, в то время как VS и я предполагаю, что ICC этого не делают. является ли typedef typename "ошибкой" или чрезмерно сжатым стандартом, или чем-то, что остается на усмотрение авторов компилятора?
Для тех, кто не знает, что я имею в виду, вот пример:
template<typename KEY, typename VALUE>
bool find(const std::map<KEY,VALUE>& container, const KEY& key)
{
std::map<KEY,VALUE>::const_iterator iter = container.find(key);
return iter!=container.end();
}
Приведенный выше код компилируется в VS (и, вероятно, в ICC), но терпит неудачу в GCC, потому что он этого хочет вот так:
template<typename KEY, typename VALUE>
bool find(const std::map<KEY,VALUE>& container, const KEY& key)
{
typedef typename std::map<KEY,VALUE>::const_iterator iterator; //typedef typename
iterator iter = container.find(key);
return iter!=container.end();
}
Примечание: это не фактическая функция, которую я использую, а просто что-то глупое, что демонстрирует проблему.
5 ответов:
Имя типа требуется стандартом. Компиляция шаблона требует двухэтапной проверки. Во время первого прохода компилятор должен проверить синтаксис шаблона, фактически не предоставляя замены типов. На этом шаге предполагается, что значение std::map::iterator. Если он обозначает тип, то требуется ключевое слово typename.
Почему это необходимо? Перед заменой фактических типов ключей и значений компилятор не может гарантировать, что шаблон не является специализированным и что он не содержит данных. что специализация не переопределяет ключевое словоитератора как что-то другое.
Вы можете проверить это с помощью следующего кода:
class X {}; template <typename T> struct Test { typedef T value; }; template <> struct Test<X> { static int value; }; int Test<X>::value = 0; template <typename T> void f( T const & ) { Test<T>::value; // during first pass, Test<T>::value is interpreted as a value } int main() { f( 5 ); // compilation error X x; f( x ); // compiles fine f: Test<T>::value is an integer }
Последний вызов завершается ошибкой, указывающей на то, что на первом шаге компиляции шаблона f() Test::value был интерпретирован как значение, но экземпляр шаблона Test с типом X дает тип.
Ну, GCC на самом деле не требует
typedef
--typename
вполне достаточно. Это работает:#include <iostream> #include <map> template<typename KEY, typename VALUE> bool find(const std::map<KEY,VALUE>& container, const KEY& key) { typename std::map<KEY,VALUE>::const_iterator iter = container.find(key); return iter!=container.end(); } int main() { std::map<int, int> m; m[5] = 10; std::cout << find(m, 5) << std::endl; std::cout << find(m, 6) << std::endl; return 0; }
Это пример проблемы контекстно-зависимого синтаксического анализа. Что означает рассматриваемая строка, не видно только из синтаксиса этой функции - вам нужно знать, является ли
Теперь я, кажется, не могу придумать пример чего ...std::map<KEY,VALUE>::const_iterator
типом или нет.::const_iterator
может быть, кроме типа, это также не будет ошибкой. Поэтому я думаю, что компилятор может узнать, что он имеет быть типом, но это может быть трудно для бедного компилятора (писателей).Стандарт требует использования
typename
здесь, согласно litb в разделе 14.6/3 стандарта.
Похоже, что VS / ICC поставляет ключевое слово
typename
везде, где оно считает необходимым. Обратите внимание, что это плохая вещь (TM) - позволить компилятору решить, что Вы хотите. Это еще больше усложняет проблему, прививая плохую привычку пропускатьtypename
, когда это требуется, и является кошмаром переносимости. Это определенно не стандартное поведение. Попробуйте в строгом стандартном режиме или Comeau.
Это ошибка в компиляторе Microsoft C++ - в вашем примере std:: map:: iterator может не быть типом (вы могли бы специализировать std::map на ключе,значении, чтобы std::map::iterator был переменной, например).
GCC заставляет вас писать правильный код (даже если то, что вы имели в виду, было очевидно), в то время как компилятор Microsoft правильно угадывает, что вы имели в виду (даже если написанный вами код был неверным).
Следует отметить, что проблема подбора значения/типа не является фундаментальной проблемой. Основной проблемой являетсясинтаксический анализ . Рассмотрим
template<class T> void f() { (T::x)(1); }
Невозможно определить, является ли это приведением или вызовом функции, если только ключевое слово typename не является обязательным. В этом случае приведенный выше код содержит вызов функции. В общем случае выбор не может быть отложен без отказа от разбора вообще, просто рассмотрим фрагмент
(a)(b)(c)
В случае, если вы не помните, приведение имеет более высокий приоритет, чем вызов функции в C, одна из причин, по которой Бьярне хотел кастовать стиль функции. Поэтому невозможно сказать, означает ли вышеизложенное
(a)(b) (c) // a is a typename
Или
(a) (b)(c) // a is not a typename , b is
Или
(a)(b) (c) // neither a nor b is a typename
Где я вставил пробел, чтобы указать группировку.
Примечание также ключевое слово " templatename "требуется по той же причине, что и" typename", вы не можете анализировать вещи, не зная их вида в C/C++.