Почему я должен использовать typedef typename в g ++, а не VS? - PullRequest
50 голосов
/ 13 марта 2009

Прошло много времени с тех пор, как GCC поймал меня с этим, но это произошло сегодня. Но я никогда не понимал, почему GCC требует typedef typename в шаблонах, а VS и я полагаю, что ICC этого не делает. Является ли typepef 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 ]

56 голосов
/ 13 марта 2009

Типовое имя требуется стандартом. Компиляция шаблона требует двухэтапной проверки. Во время первого прохода компилятор должен проверить синтаксис шаблона без фактической замены типов. На этом шаге предполагается, что 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 :: value был интерпретирован как значение, но создание шаблона Test <> с типом X дает тип.

32 голосов
/ 13 марта 2009

Ну, 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 стандарта.

4 голосов
/ 13 марта 2009

Похоже, VS / ICC поставляет ключевое слово typename везде, где оно считает необходимым. Обратите внимание, что это плохая вещь (TM) - позволить компилятору решать, что вы хотите. Это еще более усложняет проблему, прививая дурную привычку пропускать typename, когда это необходимо, и это кошмар переносимости. Это определенно не стандартное поведение. Попробуйте в строгом стандартном режиме или Comeau.

3 голосов
/ 13 марта 2009

Это ошибка в компиляторе Microsoft C ++ - в вашем примере std :: map :: iterator может не являться типом (у вас может быть специализированная std :: map для KEY, VALUE, так что std :: map :: итератор был переменной например).

GCC заставляет вас писать правильный код (хотя то, что вы имели в виду, было очевидно), тогда как компилятор Microsoft правильно угадывает, что вы имели в виду (даже если код, который вы написали, был неверным).

2 голосов
/ 30 ноября 2010

Следует отметить, что проблема с сортировкой значений / типов не является фундаментальной проблемой. Основная проблема - разбор . Рассмотрим

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 ++.

...