Снова о типе имени и ключевых словах шаблона

Я внимательно прочитал много ответов, касающихся этой темы, но, тем не менее, я не могу ТОЧНО понять, когда эти два ключевых слова ЯВЛЯЮТСЯ или НЕ нужны в рамках функции, не являющейся шаблоном, которая является членом вложенного класса шаблона.

Моими эталонными компиляторами являются 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> ?

И, в конце концов, почему такое разное поведение двух цитируемых компиляторов?

Кто-нибудь может пояснить решение загадки?


person GSi    schedule 03.09.2015    source источник
comment
Они ведут себя едва ли по-разному... как я понимаю ваш вопрос, вы удивляетесь, почему они ведут себя по-разному, в данном случае вряд ли это неправильное слово. На самом деле то, что вы описываете, это то, что они вряд ли ведут себя одинаково;)   -  person 463035818_is_not_a_number    schedule 03.09.2015
comment
GCC, кажется, больше не может справиться с именем, как только вы добавляете typename или template и забываете о том, что P<AAA> является окружающим шаблоном. Я думаю, что у GCC есть ошибка, и я рекомендую сделать PR. Здесь нет необходимости в typename или template.   -  person Johannes Schaub - litb    schedule 03.09.2015
comment
Недавно были смягчены правила разрешения typename там, где это строго не требуется. Вероятно, после выпуска gcc 4.9. Можно пояснить пример 3.   -  person Bo Persson    schedule 03.09.2015
comment
Pa<double>::Pe<BBB> является зависимым именем в том смысле, что его тип зависит от BBB. Но это не значит, что вы должны вставлять typename. Это верно также для Pa<AAA>::Pe<int> и Pa<AAA>. Во всех этих случаях компилятор может сам определить, что Pe — это шаблон, а Pe<int>/Pe<BBB> — это тип. См. stackoverflow.com/a/17579889/34509.   -  person Johannes Schaub - litb    schedule 03.09.2015


Ответы (1)


Важным правилом в [temp.res] является:

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

Вопрос касается двух квалифицированных идентификаторов:

Pa<double>::Pe<BBB>
Pa<AAA>::Pe<int>

Во-первых, что такое зависимый тип? Согласно [temp.dep.type]:

Тип является зависимым, если он
— параметр шаблона,
— член неизвестной специализации,
— вложенный класс или перечисление, являющееся зависимым членом текущего экземпляра,
— элемент cv-qualified тип, где cv-unqualified тип является зависимым,
— составной тип, созданный из любого зависимого типа,
— тип массива, тип элемента которого является зависимым или чья граница (если есть) зависит от значения,
simple-template-id, в котором либо имя шаблона является параметром шаблона, либо любой из аргументов шаблона является зависимым типом или выражением, зависящим от типа или значения, или
— обозначается decltype(выражение), где выражение зависит от типа (14.6.2.2).

Pa<double> (описатель вложенного имени из первого примера) не является зависимым типом, так как не соответствует ни одному из пунктов списка. Поскольку мы не соответствуем этим критериям, нам не нужно добавлять префикс к ключевому слову typename.

Pa<AAA>, однако, является зависимым типом, так как это simple-template-id, в котором один из аргументов шаблона является зависимым типом (AAA тривиально является зависимым типом, поскольку это параметр шаблона).

Что же такое «член текущего экземпляра»?

Имя относится к текущему экземпляру, если оно
— [...]
— в определении шаблона основного класса или члена шаблона основного класса, имя шаблон класса, за которым следует список аргументов шаблона основного шаблона (как описано ниже), заключенный в ‹> (или эквивалентную специализацию псевдонима шаблона)" — в определении вложенного класса шаблона класса имя вложенного класса упоминается как член текущего экземпляра, или

Текущее воплощение в данном случае — Pa<AAA> (или также Pa). А также:

Имя является членом текущего экземпляра, если оно [...] представляет собой квалифицированный-id, в котором описатель вложенного имени относится к текущему экземпляру и что , при поиске относится по меньшей мере к одному члену класса, который является текущим экземпляром, или к его независимому базовому классу.

Таким образом, 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;

это ошибка.

person Barry    schedule 03.09.2015