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

时间:2014-06-04 11:36:26

标签: 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;
}

此代码在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>{}));

现在,如果我们使用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;
}

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

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

我还尝试使用更多decltypedeclval来填充代码,但我似乎无法使C ++ 11语法正常工作。

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

1 个答案:

答案 0 :(得分:16)

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

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

  

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

结果,声明template<int i> constexpr auto iter(...) -> ...i = 0实例化(14.7.1p10 [temp.inst] ),强制评估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{}));
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        ^^^^^^^

请注意,SFINAE必须 decltype内部,而且实际上是在iter的调用中。