【问题标题】:Why does alias template give a conflicting declaration?为什么别名模板给出冲突的声明?
【发布时间】:2017-01-13 19:45:50
【问题描述】:

部分 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;

如果我使用完整的typename S&lt;T&gt;::value_type 而不是模板别名value_t&lt;S&lt;T&gt;&gt;,那么g++ also works

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

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

【问题讨论】:

  • 当然看起来它们应该是等价的:eel.is/c++draft/temp.alias#2
  • 我将使用 500 的编译器错误,Alex
  • 我不认为这是微不足道的。关于别名模板的依赖类型等价存在很多问题。请参阅open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1979 以及与之相关的所有问题。答案应该涵盖这个问题以及这些问题在这个问题上的相关性,IMO。
  • 它并不止于此。关于 typename S&lt;T&gt;::value_type 案例是否有效,还有一个由来已久的问题,您还可以写什么:open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2
  • 我认为在编译器中,别名模板在 14.5.6.1p5 的“等效”检查完成之前被替换。所以value_t&lt;foobar&gt; 应该等价于foobar::value_type,即使涉及依赖类型。这也是 Barry 引用的段落所说的(它使用“等效”,尽管没有对 14.5.6.1p5 的脚注,这可以澄清它的意思是指“等效”的含义)。

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


【解决方案1】:

问题依赖于 SFINAE。如果你将你的成员函数重写为value_t&lt;S&lt;T&gt;&gt;,就像外部声明一样,那么 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&lt;S&lt;T&gt;&gt; const S&lt;T&gt;::C 相同的“原型”。第一个不必执行 SFINAE,而第二个则需要它。很明显,这两种声明都有不同的功能,因此 GCC 会发脾气。

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

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

当template-id指代一个别名模板的特化时,它等价于用它的template-arguments替换别名模板的type-id中的template-parameters得到的关联类型。

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

template<class T>
struct Alloc { /* ... */ };

template<class T>
using Vec = vector<T, Alloc<T>>;

Vec<int> v;
vector<int, Alloc<int>> u;

Vec&lt;int&gt;vector&lt;int, Alloc&lt;int&gt;&gt; 是等价的类型,因为执行替换后,这两种类型最终都是vector&lt;int, Alloc&lt;int&gt;&gt;。请注意“替换后”如何意味着只有在所有模板参数都被模板参数替换后才检查等价性。也就是说,当vector&lt;T, Alloc&lt;T&gt;&gt; 中的TVec&lt;int&gt; 中的int 替换时,比较开始。也许这就是 Clang 对 value_t&lt;S&lt;T&gt;&gt; 所做的事情?但是下面是来自[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) -&gt; std::enable_if_t&lt;sizeof(T) == 4&gt; 反而使代码编译得很好。之所以如此,是因为foo的返回类型是一个依赖于sizeof(T) == 4结果的表达式,所以模板替换后,它的原型可能与它的每个实例不同。而auto X::foo(T) -&gt; void 的返回类型永远不会不同,这与X 中的声明冲突。这与您的代码发生的问题完全相同。所以 GCC 在这种情况下似乎是正确的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-20
    • 2019-03-04
    • 2021-08-11
    • 2012-08-06
    相关资源
    最近更新 更多