Почему мне нужно использовать 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/С++.