我在显式和自动尾随返回类型之间发现了一个奇怪的区别。
在下面的代码中,我们定义了一个在整数和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;
}
这现在适用于两个编译器并且按预期运行。
我尝试了不同的方法来表达专业化并稍微移动它(要小心它的位置),但这并没有阻止它的自焚;(
我还尝试使用更多decltype
和declval
来填充代码,但我似乎无法使C ++ 11语法正常工作。
有人可以解释名称查找的两种语法之间的区别吗?
答案 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
的调用中。