2017-01-13 4 views
41

Порт некоторых C++ 11 кода из Clang в г ++Почему шаблон псевдонима дает противоречивую декларацию?

template<class T> 
using value_t = typename T::value_type; 

template<class> 
struct S 
{ 
    using value_type = int; 
    static value_type const C = 0; 
}; 

template<class T> 
value_t<S<T>> // gcc error, typename S<T>::value_type does work 
const S<T>::C; 

int main() 
{  
    static_assert(S<int>::C == 0, ""); 
} 

дает различное поведение для звона (версий 3.1 через ствол SVN) по сравнению с любой версией г ++. В последнем случае я получаю ошибку like this

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

Если вместо шаблона псевдонима value_t<S<T>> Я использую полный typename S<T>::value_type затем g++ also works.

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

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

+4

Несомненно, они должны быть эквивалентны: http://eel.is/c++draft/temp.alias#2 – Barry

+4

Я пойду с ошибкой компилятора за 500, Alex – AndyG

+8

Я не думаю, что это тривиально , Существует много вопросов об эквивалентности зависимых типов в отношении шаблонов псевдонимов. См. Http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1979 и все связанные с ним проблемы. Ответ должен охватывать этот вопрос и актуальность этих вопросов по этому вопросу, ИМО. –

ответ

6

Проблема связана с 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 кажется правильным в этом случае.