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

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

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

最佳答案

问题依赖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 相同的“原型(prototype)” .第一个不必执行 SFINAE,而第二个则需要它。很明显,这两个声明具有不同的功能,因此 GCC 大发雷霆。

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

现在,从标准的角度来看,哪一个是正确的?是否将别名模板的 SFNIAE 视为其声明功能的一部分仍然是一个 Unresolved 问题。引用 [temp.alias]/2 :

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.

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

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> .也许这就是 Clang 对 value_t<S<T>> 所做的?但是下面是来自 [temp.alias]/3 的引述:

However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id. [Example:

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

 — end example]

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

关于c++ - 为什么别名模板会给出冲突的声明?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44463992/

相关文章:

javascript - HTML 模板不适用于 For-In 循环 JavaScript

C++:模板类二元运算符重载 - 段错误?

c++ - 如何从构造函数返回错误代码?

c++ - c++中的扩展方法

c++ - munmap_chunk() : invalid pointer when using function templates

c++ - 为什么大数输入会导致此代码计算 primeCounting 时产生运行时错误?

具有数组类型的 C++ add_pointer 模板

c++ - CaptureStackBackTrace 是单线程的吗?

C++和MPI如何并行编写部分代码?

c++ - C(和 C++)中 char 的对齐是否保证为 1?