Проблема связана с SFINAE. Если вы переписать вашу функцию член быть value_t<S<T>>
, как вне декларации, то GCC будет счастливо скомпилировать:
template<class T>
struct S
{
using value_type = int;
static const value_t<S<T>> C = 0;
};
template<class T>
const value_t<S<T>> S<T>::C;
Поскольку выражение теперь функционально эквивалентны. Такие вещи, как сбой замены вступают в игру на шаблонах псевдонимов, но, как вы видите, функция-член value_type const C
не имеет такого же «прототипа», как value_t<S<T>> const S<T>::C
. Во-первых, не нужно выполнять SFINAE, тогда как второй требует. Таким образом, обе декларации имеют разную функциональность, поэтому истерика GCC.
Интересно, что Clang компилирует его без признаков ненормальности. Я предполагаю, что так получилось, что порядок анализов Клана изменился, по сравнению с GCC. После того, как выражение шаблона alias будет разрешено и точным (то есть оно хорошо сформировано), clang затем сравнивает обе декларации и проверяет, что они эквивалентны (что в этом случае они, если оба выражения разрешены на value_type
).
Теперь, какой из них является правильным из глаз стандарта? По-прежнему нерешенным является вопрос о том, следует ли рассматривать SFNIAE псевдонима-шаблона как часть функциональности его декларации. Цитирование [temp.alias]/2:
Когда шаблон Идентификатор относится к специализации шаблона псевдонима, это равносильно тому, ассоциированного типа, полученного путем замены его шаблонов аргументов для шаблона-параметров в тип-идентификатор шаблон псевдонима.
Другими словами, эти два эквивалентны:
template<class T>
struct Alloc { /* ... */ };
template<class T>
using Vec = vector<T, Alloc<T>>;
Vec<int> v;
vector<int, Alloc<int>> u;
Vec<int>
и vector<int, Alloc<int>>
эквивалентные типы, потому что после подстановки выполняется, оба типа в конечном итоге vector<int, Alloc<int>>
. Обратите внимание, что «после подстановки» означает, что эквивалентность проверяется только после того, как все аргументы шаблона заменяются параметрами шаблона. То есть, сравнение начинается, когда T
в vector<T, Alloc<T>>
заменяется на int
от Vec<int>
. Может быть, это то, что делает Кланг с value_t<S<T>>
?Но тогда есть следующая цитата из [temp.alias]/3:
Однако, если идентификатор шаблона зависит, последующая замена шаблона шаблона по-прежнему применяется к идентификатору шаблона. [Пример:
template<typename...> using void_t = void;
template<typename T> void_t<typename T::foo> f();
f<int>(); // error, int does not have a nested type foo
- конец пример]
Вот проблема: выражение имеет быть хорошо сформированы, так что компилятор должен проверить, является ли замена в порядке. Когда существует зависимость для выполнения замены шаблона аргумента (например, typename T::foo
), функциональность всего выражения изменяется, и определение «эквивалентность» отличается. Например, следующий код не компилируется (GCC и Clang):
struct X
{
template <typename T>
auto foo(T) -> std::enable_if_t<sizeof(T) == 4>;
};
template <typename T>
auto X::foo(T) -> void
{}
Поскольку внешние foo
«ы прототип функционально отличается от внутреннего. Выполнение auto X::foo(T) -> std::enable_if_t<sizeof(T) == 4>
вместо этого делает код компилируемым. Это связано с тем, что возвращаемый тип foo
является выражением, которое зависит от результата sizeof(T) == 4
, поэтому после замены шаблона его прототип может отличаться от каждого его экземпляра. В то время как тип возврата auto X::foo(T) -> void
никогда не отличается, что противоречит декларации внутри X
. Это та самая проблема, что происходит с вашим кодом. Таким образом, GCC кажется правильным в этом случае.
Несомненно, они должны быть эквивалентны: http://eel.is/c++draft/temp.alias#2 – Barry
Я пойду с ошибкой компилятора за 500, Alex – AndyG
Я не думаю, что это тривиально , Существует много вопросов об эквивалентности зависимых типов в отношении шаблонов псевдонимов. См. Http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1979 и все связанные с ним проблемы. Ответ должен охватывать этот вопрос и актуальность этих вопросов по этому вопросу, ИМО. –