递归尾随返回类型的名称解析

Thi*_*aut 21 c++ c++11 c++14

我在显式和自动尾随返回类型之间发现了一个奇怪的区别.

在下面的代码中,我们定义了一个在整数和iter函数上模板化的结构,它将这种类型的一个对象作为参数.返回类型取决于在递减模板值后调用自身的结果.

为了打破实例化循环(或者我认为),我提供了一个返回非依赖类型的特化.

我们有一个玩具主体来实例化模板.

这是一些代码:

template<int i> struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));

int main(){
  decltype(iter(Int<10>{})) a;
}
Run Code Online (Sandbox Code Playgroud)

此代码在gcc 4.9和clang 3.5中都不起作用.两者都触发无限实例化(它们与专用基本情况不匹配).

rec.cpp:11:62: fatal error: recursive template instantiation exceeded maximum depth of 256
template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));
Run Code Online (Sandbox Code Playgroud)

现在,如果我们使用C++ 14 decltype(auto)并且我们为模板提供了一个返回完全相同的东西:

template<int i> struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) {
  return iter(Int<i-1>{});
}

int main(){
  decltype(iter(Int<10>{})) a;
}
Run Code Online (Sandbox Code Playgroud)

这现在适用于两个编译器并按预期运行.

我尝试了不同的方式来表达专业化并稍微移动一下(要小心它的位置),但这并没有阻止它的自焚;(

我也试图撒上更多的代码decltypedeclval,但我似乎无法得到C++语法11工作.

有人可以解释名称查找的两种语法之间的区别吗?

eca*_*mur 16

这是因为重载决策,模板重载解析,模板声明实例化和模板定义实例化的相对排序.

我们先来看看C++ 11案例.当编译器需要进行求值时decltype(iter(Int<0>{})),它会对iter使用参数prvalue调用的名称执行重载解析Int<0>.由于模板在重载集中,我们应用14.8.3 [temp.over]:

1 - 函数模板可以通过其名称的(非模板)函数或同名的(其他)函数模板重载.当写入对该名称的调用(显式或隐式使用运算符表示法)时,将为每个函数模板执行模板参数推导(14.8.2)和任何显式模板参数检查(14.3)以查找模板参数值( if if)可以与该函数模板一起使用来实例化可以使用调用参数调用的函数模板特化.[...]

结果,声明template<int i> constexpr auto iter(...) -> ...被实例化(14.7.1p10 [temp.inst])i = 0,这强制评估decltype(iter(Int<-1>{}))和关闭我们去的负整数的兔子洞.

没关系,这constexpr auto iter(Int<0>) -> Int<0>将是一个更好的过载(由13.3.3p1 [over.match.best]),因为我们永远不会那么远; 编译器正朝着负无穷远的方向快步走.

相比之下,使用C++ 14推导的返回类型7.1.6.4p12 [dcl.spec.auto]适用:

12 - 当实例化定义时,会发生函数模板的返回类型推导,其中包含声明类型的占位符[...]

由于定义实例化发生模板重载解析(14.7.1p3)之后,因此iter<0>从不实例化坏模板; 14.8.3p5:

5 - 只需要函数模板特化的签名即可在一组候选函数中输入特化.因此,仅需要函数模板声明来解析模板特化是候选的调用.

这里的"签名" iter<0>(Int<0>) -> decltype(auto)包含占位符类型的签名(7.1.6.4).


建议的解决方法:使用SFINAE防止任何尝试呼叫iter(Int<-1>{}):

template<int i> constexpr auto iter(Int<i>)
  -> decltype(iter(typename std::enable_if<i != 0, Int<i-1>>::type{}));
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        ^^^^^^^
Run Code Online (Sandbox Code Playgroud)

需要注意的是SFINAE已去里面decltype,确实调用里面iter.

  • @Adrian同样的理由; 这里`iter <0>`声明的实例化强制实例化`iter <0>`的相同声明,依此类推; 编译器错误消息应该告诉你这个.除了通常的深度限制之外,模板重载决策中没有打破周期的规则. (2认同)
  • @Nawaz它确实是可见的,但是编译器必须实例化递归情况`template&lt;int i&gt; constexpr auto iter(Int&lt;i&gt;) -&gt; decltype(iter(Int&lt;i-1&gt;{}));` `[i = 0]` 无论如何,检查它是否是一个可行的重载。 (2认同)