Может ли typename быть опущено в спецификаторе типа определения члена вне строки?

Я столкнулся с этим странным поведением, проверяя, требуется ли typename клану. Оба clang и gcc принимают этот код, а msvc отклоняет его.

template<class T1>
struct A
{
    template<class T2>
    struct B
    {
        static B f;
        static typename A<T2>::template B<T1> g;
    };
};

template<class T1>
template<class T2>
typename A<T2>::template B<T1> // ok, typename/template required
    A<T1>::B<T2>::g;

template<class T1>
template<class T2>
A<T1>::B<T2> // clang/gcc accept, msvc rejects missing typename
    A<T1>::B<T2>::f;

В общем случае квалифицированный идентификатор A<T1>::B<T2> (где A<T1> является зависимым именем) должен быть записан typename A<T1>::template B<T2>. Является ли поведение gcc/clang некорректным или существует исключение из общего правила (приведенного ниже) в данном конкретном случае?

Можно утверждать, что A<T1> не является зависимым именем или что B<T2> относится к члену текущего экземпляра. Однако в момент разбора спецификатора типа невозможно узнать, что текущее создание A<T1>. Представляется проблематичным требовать, чтобы реализация предполагала, что A<T1> является текущим экземпляром.

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

Имя, используемое в объявлении или определении шаблона, и которое зависит от параметра шаблона, предполагается, что не следует указывать тип, если соответствующий поиск имени не найдет имя типа или имя не будет квалифицировано по ключевому слову typename.

14.2 Имена специализированных шаблонов [temp.names]

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

Чтобы продолжить исследование того, что делает clang, я также пробовал это:

template<class T1>
struct C
{
    template<class T2>
    struct D
    {
        static typename A<T1>::template B<T2> f;
        static typename A<T1>::template B<T2> g;
    };
};

template<class T1>
template<class T2>
typename A<T1>::template B<T2> // ok, typename/template required
    C<T1>::D<T2>::f;

template<class T1>
template<class T2>
A<T1>::B<T2> // clang rejects with incorrect error
    C<T1>::D<T2>::g;

Clang дает error: redefinition of 'g' with a different type, но тип g действительно соответствует объявлению.

Вместо этого я ожидал увидеть диагностику, предполагающую использование typename или template.

Это дает основание полагать, что поведение clang в первом примере непреднамеренно.

Ответ 1

clang и gcc верны.

Компилятор знает, что A<T1>::B<T2> относится к типу, а B<T2> является шаблоном и что A<T1>::B<T2>::f является членом текущего экземпляра. Поэтому ключевые слова typename и template не нужны.

Из v14.6.2.1p4:

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

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

A<T1>::B<T2> - это квалифицированный идентификатор, а A<T1>:: - это вложенный имя-спецификатор, который ссылается на текущую копию. Мы знаем, что A<T1>:: относится к текущему экземпляру из 14.6.2.1p1:

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

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

В вашем коде мы имеем определение члена шаблона первичного класса, то есть A<T1>::B<T2>::f, а A<T1> - это имя шаблона класса, за которым следует список аргументов шаблона основного шаблона.

В вашем вопросе вы скажете However, at the point of parsing the type-specifier it not possible to know that the current instantiation is A<T1>. Тем не менее, я не могу следовать этому, потому что имя A<T1> ссылается на текущую инстанцировку, как указано выше.

Ответ 2

MSVC правильный.

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

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

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

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

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

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

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

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

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

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

Однако, когда синтаксический анализатор С++ встречает A<T1> в возвращаемом типе определения функции-члена - перед идентификатором-идентификатором - он еще не встретил имя охватывающего класса. Синтаксический анализатор не может определить, относится ли A к охватывающему классу в данный момент.

По этой причине - независимо от того, указывает ли или нет A<T1> текущий экземпляр - стандарт не позволяет исключить 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'
{
}