为什么别名模板给出了相冲突的声明?

从Clang到g ++的一些C ++ 11代码的端口

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, ""); } 

为Clang(版本3.1至SVN中继)与任何g ++版本提供不同的行为。 对于后者,我得到这样的错误

 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 ++也可以

问题 :模板别名是否应该与其基础expression式完全互换? 这是一个g ++的错误?

更新 :Visual C ++也接受在类外定义中的别名模板。

这个问题依赖于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; 

因为expression式现在在function上是等同的。 诸如replace失败之类的东西在别名模板上起作用,但正如你所见,成员函数value_type const Cvalue_t<S<T>> const S<T>::C不具有相同的“原型”。 第一个不需要执行SFINAE,而第二个则需要。 所以显然这两个声明有不同的function,因此海合会的发脾气。

有趣的是,铿锵编译它没有exception的迹象。 我认为恰恰相反,Clang的分析顺序与GCC的顺序相反。 一旦别名模板expression式被parsing并罚款(即格式良好),clang就会比较这两个声明并检查它们是否相等(在这种情况下,它们是,因为这两个expression式都parsing为value_type )。

现在,从标准的眼睛看哪个是正确的? 将alias-template的SFNIAE视为其声明function的一部分,仍然是一个尚未解决的问题。 引用[temp.alias] / 2 :

当template-id引用别名模板的特化时,相当于在别名模板的type-id中replace模板参数的模板参数所获得的关联types。

换句话说,这两个是等价的:

 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>>是等价的types,因为在执行replace之后,两个types最终都是vector<int, Alloc<int>> 。 请注意,“replace后”的意思是一旦所有的模板参数被replace为模板参数,就只会检查等价性。 也就是说,当vector<T, Alloc<T>>被来自Vec<int> intreplace时,比较开始。 也许这就是value_t<S<T>>value_t<S<T>>做的事情? 但是接下来是[temp.alias] / 3 :

但是,如果template-id是依赖的,则后续的模板参数replace仍然适用于template-id。 [例:

 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 

– 结束示例]

这里的问题是:expression式必须是格式良好的,所以编译器需要检查replace是否正确。 当为了执行模板参数replace(例如, typename T::foo )而存在依赖关系时,整个expression式的function改变,并且“等同性”的定义不同。 例如,下面的代码不会编译(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的原型在function上与内部不同。 做auto X::foo(T) -> std::enable_if_t<sizeof(T) == 4>而是使代码编译正常。 这是因为foo的返回types是依赖于sizeof(T) == 4的结果的expression式,所以在模板replace之后,其原型可能与其每个实例不同。 而auto X::foo(T) -> void的返回types永远不会不同,这与X的声明冲突。 这与你的代码发生的问题是一样的。 所以GCC在这种情况下似乎是正确的。