Почему шаблон псевдонимов дает противоречивую декларацию?

Порт некоторого кода C ++ 11 от Clang до g ++

template using value_t = typename T::value_type; template struct S { using value_type = int; static value_type const C = 0; }; template value_t<S> // gcc error, typename S::value_type does work const S::C; int main() { static_assert(S::C == 0, ""); } 

дает различное поведение для Clang (версии 3.1 через соединительную линию SVN) по сравнению с любой версией g ++. Для последнего я получаю такие ошибки

 prog.cc:13:13: error: conflicting declaration 'value_t<S > S<  >::C' const S::C; ^ prog.cc:8:29: note: previous declaration as 'const value_type S<  >::C' static value_type const C = 0; ^ prog.cc:13:13: error: declaration of 'const value_type S<  >::C' outside of class is not definition [-fpermissive] const S::C; 

Если вместо шаблона alias value_t<S> я использую полное typename S::value_type тогда работает g ++ .

Вопрос : не являются ли шаблонные псевдонимы полностью взаимозаменяемыми с их основным выражением? Это ошибка g ++?

Обновление : Visual C ++ также принимает шаблон псевдонима в определении вне classа.

    Проблема связана с SFINAE. Если вы переписываете свою функцию-член как value_t> , как и внешнюю декларацию, GCC с радостью скомпилирует ее:

     template struct S { using value_type = int; static const value_t> C = 0; }; template const value_t> S::C; 

    Потому что выражение теперь функционально эквивалентно. Такие вещи, как сбой замещения, вступают в игру на шаблонах псевдонимов, но, как вы видите, функция-член value_type const C не имеет того же «прототипа», что и value_t> const S::C Во-первых, не нужно выполнять SFINAE, тогда как второй требует. Таким образом, обе декларации имеют разную функциональность, поэтому истерика GCC.

    Интересно, что Кланг компилирует его без признаков ненормальности. Я предполагаю, что так получилось, что порядок анализов Клана изменился, по сравнению с GCC. После того как выражение шаблона alias будет разрешено и точным (то есть оно хорошо сформировано), clang затем сравнивает обе декларации и проверяет, что они эквивалентны (что в этом случае они, если оба выражения разрешают value_type ).

    Теперь, какой из них правильный из глаз стандартного? По-прежнему нерешенным является вопрос о том, следует ли рассматривать SFNIAE псевдонима-шаблона как часть функциональности его декларации. Цитирование [temp.alias] / 2 :

    Когда идентификатор шаблона ссылается на специализацию шаблона псевдонима, он эквивалентен связанному типу, полученному путем подстановки его шаблонных аргументов для параметров шаблона в идентификаторе типа шаблона псевдонимов.

    Другими словами, эти два эквивалента:

     template struct Alloc { /* ... */ }; template using Vec = vector>; Vec v; vector> u; 

    Vec и vector> являются эквивалентными типами, потому что после подстановки оба типа заканчиваются vector> . Обратите внимание, что «после подстановки» означает, что эквивалентность проверяется только после того, как все аргументы шаблона заменяются параметрами шаблона. То есть сравнение начинается, когда T в vector> заменяется на int из Vec . Может быть, это то, что делает value_t> с value_t> ? Но тогда есть цитата из [temp.alias] / 3 :

    Однако, если идентификатор шаблона зависит, последующая замена аргумента шаблона по-прежнему применяется к идентификатору шаблона. [Пример:

     template using void_t = void; template void_t f(); f(); // error, int does not have a nested type foo 

    – конец примера]

    Вот проблема: выражение должно быть хорошо сформировано, поэтому компилятору необходимо проверить, хорошо ли подстановка. Когда есть зависимость для выполнения замены аргумента шаблона (например, typename T::foo ), функциональность всего выражения меняется, а определение «эквивалентность» отличается. Например, следующий код не будет компилироваться (GCC и Clang):

     struct X { template  auto foo(T) -> std::enable_if_t; }; template  auto X::foo(T) -> void {} 

    Поскольку прототип внешнего foo функционально отличается от внутреннего. Выполнение auto X::foo(T) -> std::enable_if_t вместо этого делает компиляцию кода отлично. Это потому, что возвращаемый тип foo является выражением, которое зависит от результата sizeof(T) == 4 , поэтому после замены шаблона его прототип может отличаться от каждого его экземпляра. Принимая во внимание, что тип возвращаемого типа auto X::foo(T) -> void никогда не отличается, что противоречит декларации внутри X Это та самая проблема, что происходит с вашим кодом. Таким образом, GCC кажется правильным в этом случае.