41

将一些 C++11 代码从 Clang 移植到 g++

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

问题:模板别名不应该与其底层表达式完全互换吗?这是一个 g++ 错误吗?

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

4

1 回答 1

5

这个问题依赖于 SFINAE。如果你将你的成员函数重写为 be 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 Cvalue_t<S<T>> const S<T>::C. 第一个不必执行 SFINAE,而第二个则需要它。很明显,这两个声明都有不同的功能,因此 GCC 发脾气。

有趣的是,Clang 编译它没有任何异常迹象。我认为与 GCC 相比,Clang 的分析顺序正好相反。一旦别名模板表达式被解析并正常(即格式正确),clang 然后比较两个声明并检查它们是否等效(在这种情况下它们是等效的,因为两个表达式都解析为value_type)。

现在,从标准的角度来看,哪一个是正确的?是否将 alias-template 的 SFNIAE 作为其声明功能的一部分仍然是一个未解决的问题。引用[temp.alias]/2

当 template-id 指代别名模板的特化时,它等价于通过将其模板参数替换为别名模板的 type-id 中的模板参数而获得的关联类型。

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

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>>. 请注意“替换后”如何意味着只有在所有模板参数都被模板参数替换后才检查等价性。也就是说,比较在将Tinvector<T, Alloc<T>>替换为intfrom时开始Vec<int>。也许这就是 Clang 正在做的事情value_t<S<T>>?但是下面是来自[temp.alias]/3的引用:

但是,如果模板 ID 是依赖的,则后续模板参数替换仍适用于模板 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

 ——结束示例]

这就是问题所在:表达式必须格式正确,因此编译器需要检查替换是否正确。当为了执行模板参数替换而存在依赖时(例如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>会使代码编译得很好。之所以如此,是因为 of 的返回类型foo是一个依赖于 的结果的表达式sizeof(T) == 4,所以在模板替换之后,它的原型可能与它的每个实例不同。而auto X::foo(T) -> void' 的返回类型永远不会不同,这与 . 内部的声明相冲突X。这与您的代码发生的问题完全相同。所以 GCC 在这种情况下似乎是正确的。

于 2017-03-01T21:20:17.873 回答