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

时间:2017-01-13 19:45:51

标签: c++ c++11 templates g++ template-aliases

从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 ++版本的不同行为。对于后者,我得到错误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 ++也接受了类外定义中的别名模板。

1 个答案:

答案 0 :(得分: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没有相同的&#34;原型&#34;为value_t<S<T>> const S<T>::C。第一个不必执行SFINAE,而第二个需要执行SFINAE。很明显,这两个声明都有不同的功能,因此GCC会发脾气。

有趣的是,Clang编译它没有异常的迹象。我认为恰恰相反,与GCC相比,Clang的分析顺序是相反的。一旦alias-template表达式被解析并且很好(即它是格式良好的),clang然后比较两个声明并检查它们是否等效(在这种情况下它们是,在两个表达式都解析为value_type的情况下)。

现在,从标准眼中看哪一个是正确的?将别名模板的SFNIAE视为其声明功能的一部分仍然是一个尚未解决的问题。引用[temp.alias]/2

  

当template-id引用别名模板的特化时,它等同于通过替换别名模板的type-id中的template-parameters的template-arguments获得的关联类型。

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

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>>。注意替换后的&#34;&#34;表示只有在用模板参数替换所有模板参数后才会检查等价。也就是说,当T中的vector<T, Alloc<T>>替换为来自int的{​​{1}}时,比较开始。也许这就是Clang用Vec<int>做的事情?但是,[temp.alias]/3引用了以下引用:

  

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

value_t<S<T>>
     

- 结束示例]

问题在于:表达式具有格式良好,因此编译器需要检查替换是否正常。当存在依赖性以执行模板参数替换(例如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 )时,整个表达式的功能改变,并且&#34;等价的定义&#34;不同。例如,以下代码不会编译(GCC和Clang):

typename T::foo

因为外部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内的声明冲突。这与您的代码发生的问题完全相同。所以GCC在这种情况下似乎是正确的。