Почему мне нужно использовать typedef typename в g++, но не VS?
Прошло некоторое время с тех пор, как GCC поймал меня с этим, но это случилось сегодня. Но я никогда не понимал, почему GCC требует typedame typedef в шаблонах, в то время как VS и я предполагаю, что ICC этого не делают. Является ли typpedame типа typedef "ошибкой" или слишком строгим стандартом или что-то, что осталось от авторов компилятора?
Для тех, кто не знает, что я имею в виду здесь, это образец:
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();
}
Примечание. Это не фактическая функция, которую я использую, а просто что-то глупое, что демонстрирует проблему.
Ответы
Ответ 1
Типичное имя требуется по стандарту. Для компиляции шаблона требуется двухэтапная аутентификация. Во время первого прохода компилятор должен проверить синтаксис шаблона без фактического предоставления подстановок типа. На этом этапе std:: map:: iterator считается значением. Если он обозначает тип, требуется ключевое слово typename.
Почему это необходимо? Перед заменой фактических типов KEY и VALUE компилятор не может гарантировать, что шаблон не специализирован и что специализация не переопределяет ключевое слово iterator как что-то еще.
Вы можете проверить это с помощью этого кода:
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:: было интерпретировано как значение, но при создании шаблона Test < > с типом X выдается тип.
Ответ 2
Ну, 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
здесь, в соответствии с буквой раздела 14.6/3 стандарта.
Ответ 3
Похоже, VS/ICC поставляет ключевое слово typename
, где бы он ни думал, что это требуется. Обратите внимание: это Bad Thing (TM) - позволить компилятору решить, что вы хотите. Это еще более усложняет проблему, прививая плохую привычку пропускать typename
, когда это необходимо, и является кошмаром переносимости. Это определенно не стандартное поведение. Попробуйте в строгом стандартном режиме или Comeau.
Ответ 4
Это ошибка в компиляторе Microsoft С++ - в вашем примере std:: map:: iterator может не быть типом (у вас может быть специализированная std:: map на KEY, VALUE, чтобы std:: map:: итератор был переменной, например).
GCC заставляет вас писать правильный код (хотя то, что вы имели в виду, было очевидно), тогда как компилятор Microsoft правильно угадывает, что вы имели в виду (хотя код, который вы написали, был неправильным).
Ответ 5
Следует отметить, что проблема со значением типа/типа не является основной проблемой. Основная проблема - синтаксический анализ . Рассмотрим
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/С++.