Я внимательно прочитал много ответов по этой теме, но, тем не менее, я не могу точно выяснить, когда эти два ключевых слова НЕ нужны или не нужны в рамках не шаблонной функции, которая является членом вложенного шаблонного класса.
Моими эталонными компиляторами являются GNU g ++ 4.9.2 и clang 3.5.0.
Они ведут себя немного иначе в следующем коде, в который я вставил
комментарии пытаются объяснить, что происходит.
#include <iostream>
// a simple template class with a public member template struct
template <class Z>
class Pa
{
// anything
public:
template <class U>
struct Pe // a nested template
{
// anything
void f(const char *); // a non-template member function
};
template <class U> friend struct Pe;
};
// definition of the function f
template <class AAA>
template <class BBB>
void Pa<AAA> :: Pe<BBB> :: f(const char* c)
{
Pa<AAA> p; // NO typename for both clang and GNU...
// the following line is ACCEPTED by both clang and GNU
// without both template and typename keywords
// However removing comments from typename only
// makes clang still accepting the code while GNU doesn't
// accept it anymore. The same happens if the comments of template
// ONLY are removed.
//
// Finally both compilers accept the line when both typename AND
// template are present...
/*typename*/ Pa<AAA>::/*template*/ Pe<BBB> q;
// in the following clang ACCEPTS typename, GNU doesn't:
/*typename*/ Pa<AAA>::Pe<int> qq;
// the following are accepted by both compilers
// no matter whether both typename AND template
// keywords are present OR commented out:
typename Pa<int>::template Pe<double> qqq;
typename Pa<double>::template Pe<BBB> qqqq;
std::cout << c << std::endl; // just to do something...
}
int main()
{
Pa<char>::Pe<int> pp;
pp.f("bye");
}
Итак, в объеме f
является Pa<double>::Pe<BBB>
зависимое имя или нет?
И что насчет Pa<AAA>::Pe<int>
?
И, в конце концов, почему это разное поведение двух цитируемых компиляторов?
Кто-нибудь может прояснить решение головоломки?
Важное правило в [temp.res]:
Когда Квалифицированный-идентификатор предназначен для ссылки на тип, который не является членом текущего экземпляра (14.6.2.1)
И его вложенное имя спецификатор относится к зависимому типу, он должен начинаться с ключевого словаtypename
, образуя
имяТипа спецификатор. Если Квалифицированный-идентификатор в имяТипа спецификатор не обозначает тип, программа плохо сформирована.
Вопрос вращается вокруг двух Квалифицированный-идентификаторs:
Pa<double>::Pe<BBB>
Pa<AAA>::Pe<int>
Во-первых, что такое зависимый тип? Согласно [temp.dep.type]:
Тип зависит, если он
— параметр шаблона,
— член неизвестной специализации,
— вложенный класс или перечисление, являющееся зависимым членом текущего экземпляра,
— cv-квалифицированный тип, где cv-неквалифицированный тип является зависимым,
— составной тип, построенный из любого зависимого типа,
— тип массива, тип элемента которого зависит или чья граница (если есть) зависит от значения,
— а простой шаблон-идентификатор в котором либо имя шаблона является параметром шаблона, либо любой из шаблона
arguments является зависимым типом или выражением, которое зависит от типа или значения, или
— обозначаетсяdecltype
(выражение), где выражение зависит от типа (14.6.2.2).
Pa<double>
( вложенное имя спецификатор первого примера) не является зависимым типом, так как он не соответствует ни одной из маркированных точек. Поскольку мы не отвечаем этим критериям, нам не нужно ставить префикс typename
ключевое слово.
Pa<AAA>
, тем не мение, является зависимый тип, так как это простой шаблон-идентификатор в котором один из аргументов шаблона является зависимым типом (AAA
является тривиально зависимым типом, так как это параметр шаблона).
Что же тогда является «членом текущей инстанции»?
Название относится к текущая реализация если это
— […] — в определении шаблона первичного класса или члена шаблона первичного класса, имя шаблона класса, за которым следует список аргументов шаблона первичного шаблона (как описано ниже), заключенный в <> (или эквивалентная специализация псевдонима шаблона) «- в определении вложенного класса шаблона класса имя вложенного класса упоминается как
член текущей инстанции, или
Текущий экземпляр в этом случае Pa<AAA>
(или также Pa
). А также:
Имя является членом текущего экземпляра, если оно […] A Квалифицированный-идентификатор в котором вложенное имя спецификатор относится к текущему экземпляру и что при поиске относится по меньшей мере к одному члену класса, который является текущим экземпляром, или к его независимому базовому классу.
Так Pe
является членом текущего экземпляра. Таким образом, в то время как вложенное имя спецификатор из Pa<AAA>::Pe<int>
является зависимым типом, это тип, который является членом текущего экземпляра, поэтому вам не нужно ключевое слово typename
, Обратите внимание, что Pa<AAA>::Pe<int>
является сам зависимый тип (это вложенный класс, который является зависимым членом текущего экземпляра), но это само по себе означает, что typename
ключевое слово обязательно
Тот факт, что gcc здесь не принимает имя типа:
/*typename*/ Pa<AAA>::Pe<int> qq;
потому что хочет
typename Pa<AAA>::template Pe<int> qq;
это ошибка