Класс шаблона с вложенным классом шаблона, g++ компилируется нормально, vs2013 не компилируется

Я реализую свою собственную карту, и это часть кода. Часть кода, которая меня беспокоит, это два объявления:

template<typename KEY, typename VAL>
Map<KEY,VAL>::MapPair<KEY,VAL> Map<KEY,VAL>::make_map_pair(KEY k, VAL v){
    return MapPair<KEY,VAL>(k,v);
 }

template<typename KEY, typename VAL>
template<typename K, typename V>
Map<KEY,VAL>::MapPair<K,V>& Map<KEY,VAL>::MapPair<K,V>::setKey(K keyp, V val){
    key = keyp;
    value = val;
 }

Определение класса выглядит следующим образом:

 template <typename KEY, typename VAL>
 class Map{
 private:
    template<typename K, typename V>
    class MapPair {
    public:
        K key;
        V value;
        MapPair(){};
        MapPair(K key, V value);
        MapPair<K,V>& setKey(K key, V val);
        V& getValue();
        K getKey();
        bool operator==(MapPair<K,V> item);
    };

    List<MapPair<KEY,VAL>> pair_list_;
    MapPair<KEY,VAL> make_empty_map_pair(KEY k);
    MapPair<KEY,VAL> make_map_pair(KEY k, VAL v);
 public:
    Map(){}
    bool exists(KEY key);
    VAL& operator[](KEY key);
    VAL pop_pair(KEY key);
 };

Этот код компилируется без малейшего намека на g++, но Visual Studio 2013 выдает ошибки:

error C2059: syntax error : ')'
error C2059: syntax error : ')'

каждое из верхних определений функций генерирует ошибку.

Из g++ под debain 7.5

g++ -Wall -c -std=c++11  vm.cpp
g++ vm.o -o vm

и он работает хорошо, без проблем во время выполнения и делает то, что должен делать.

Вопрос в том, почему g++ работает нормально, а vs2013 не компилирует? Что я могу сделать, чтобы этот код правильно скомпилировался на vs2013? Какие привычки я могу выработать, чтобы обеспечить переносимость кода, который я пишу?


person Matt    schedule 21.12.2014    source источник
comment
тип возвращаемого значения ваших функций должен быть объявлен как typename Map<KEY,VAL>::template MapPair<K,V>   -  person Piotr Skotnicki    schedule 21.12.2014
comment
Какая именно версия g++ использовалась для компиляции этого кода?   -  person πάντα ῥεῖ    schedule 21.12.2014
comment
Это тот же вопрос, что и stackoverflow.com/questions/18344580/ .. на который не получен окончательный ответ   -  person willj    schedule 22.12.2014


Ответы (2)


Итак, оказывается, VS2013 любит вставлять typename перед возвращаемым типом вложенного класса. Я знал, что «могу» это сделать, но до этого случая никогда не знал, почему.

template<typename KEY, typename VAL>
typename Map<KEY,VAL>::MapPair<KEY,VAL> Map<KEY,VAL>::make_map_pair(KEY k, VAL v){
/*^^^^^ right here before the nested class object that it will return.*/
    return MapPair<KEY,VAL>(k,v);
}

Лучше всего использовать его, чтобы код можно было портировать с меньшими изменениями, даже если g++ этого не требует.

person Matt    schedule 21.12.2014
comment
Вам не хватает template, должно быть typename Map<KEY,VAL>::template MapPair<KEY,VAL> - person willj; 22.12.2014

Мое чтение стандарта С++ 11 предполагает, что требуется typename. VS2013 поступает правильно.

Без ключевого слова typename предполагается, что зависимое имя не является именем типа.

14.6 Разрешение имени [temp.res]

2) Предполагается, что имя, используемое в объявлении или определении шаблона и зависящее от параметра-шаблона, не является именем типа, если применимый поиск имени не находит имя типа или имя не уточняется ключевым словом typename.

3) Когда квалифицированный идентификатор предназначен для ссылки на тип, который не является членом текущего экземпляра, а его спецификатор вложенного имени ссылается на зависимый тип, перед ним должно стоять ключевое слово typename.

7) В определении шаблона класса или в определении члена шаблона класса, следующего за идентификатором-декларатором, ключевое слово typename не требуется при ссылке на имя ранее объявленного члена шаблон класса, который объявляет тип. [Примечание: такие имена можно найти с помощью поиска по неполному имени, поиска члена класса в текущем экземпляре или поиска выражения доступа к члену класса, когда тип выражения объекта является текущим экземпляром.

14.6.2.1 Зависимые типы [temp.dep.type]

Имя относится к текущему экземпляру, если оно

  • в определении шаблона основного класса или члена шаблона основного класса имя шаблона класса, за которым следует список аргументов шаблона основного шаблона (как описано ниже), заключенный в ‹>

Когда Map<KEY, VAL> используется в определении Map, это относится к текущему экземпляру. При синтаксическом анализе определения make_map_pair имя типа, дополненное Map<KEY, VAL>::, обычно можно найти с помощью поиска имени члена класса в текущем экземпляре.

Однако когда синтаксический анализатор C++ встречает Map<KEY, VAL> в возвращаемом типе определения функции-члена — до идентификатора декларатора — он еще не встречал имя окружающего класса. Синтаксический анализатор не может определить, относится ли Map к окружающему классу в этот момент.

По этой причине, независимо от того, называет ли Map<KEY, VAL> текущий экземпляр, стандарт не разрешает опускать typename в определении члена шаблона класса перед идентификатором-декларатором.

Этот пример от Vaughn Cato демонстрирует, что поведение Clang/GCC непоследовательно и требует typename в аналогичный сценарий:

template <typename T>
struct A {
    typedef int X;
    X f();
};

template <typename T>
A<T>::X A<T>::f() // error: missing 'typename'
{
}

Если мы приходим к выводу, что имя Map является зависимым и требуется typename, то также требуется ключевое слово template:

14.2 Названия специализаций шаблонов [temp.names]

Когда имя специализации шаблона-члена появляется после . или -> в постфиксном выражении или после спецификатора вложенного имени в квалифицированном идентификаторе, а также выражения объекта или указателя постфиксного выражения или спецификатора вложенного имени в квалифицированном идентификаторе зависит от параметра шаблона (14.6.2), но не относится к элементу текущего экземпляра (14.6.2.1), перед именем шаблона элемента должно стоять ключевое слово template. В противном случае предполагается, что имя не является шаблоном.

Исправленный пример будет таким:

template<typename KEY, typename VAL>
typename Map<KEY,VAL>::template MapPair<KEY,VAL>
    Map<KEY,VAL>::make_map_pair(KEY k, VAL v) {
    return MapPair<KEY,VAL>(k,v);
}

Здесь обсуждалась одна и та же проблема: -line-member-definitio">Можно ли опустить имя типа в спецификаторе типа вне строки определения члена?

person willj    schedule 22.12.2014