Можно ли опустить 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]

Когда имя специалиста шаблона члена появляется после . или же -> в выражении postfix или после спецификатора вложенного имени в квалифицированном идентификаторе, а выражение объекта или указателя в выражении постфикса или спецификаторе вложенного имени в квалифицированном идентификаторе зависит от параметра шаблона (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;

Лязг дает error: redefinition of 'g' with a different type, но тип g на самом деле соответствует декларации.

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

Это подтверждает гипотезу о том, что поведение Clang в первом примере является непреднамеренным.

3 ответа

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's not possible to know that the current instantiation is A<T1>, Тем не менее, я не могу следовать этому, потому что имя A<T1> действительно относится к текущему экземпляру, как указано выше.

MSVC правильно.

Мое чтение стандарта C++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>:: может быть найден путем поиска имени члена класса в текущем экземпляре.

Тем не менее, когда синтаксический анализатор C++ встречает A<T1> в типе возврата определения функции-члена - перед идентификатором объявления - он еще не встретил имя включающего класса. Парсер не может определить, действительно ли A относится к классу вложения в этой точке.

По этой причине - независимо от того, A<T1> называет текущую реализацию - стандарт не допускает пропуска typename в определении члена шаблона класса перед идентификатором объявления.

Этот пример Вона Катона демонстрирует, что поведение 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'
{
}

Спустя годы в C++20 такое использование typename и templateнеобязательный (хотя компиляторы еще не реализовали новые правила). Оказалось, что компиляторы должны были делать что-то вроде того, что GCC и Clang уже делали здесь, чтобы в любом случае поддерживать определения внешних конструкторов.

Другие вопросы по тегам