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

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