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

标签 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 语法正常工作。

有人能解释一下名称查找的两种语法之间的区别吗?

最佳答案

这是因为重载解析、模板重载解析、模板声明实例化和模板定义实例化的相对顺序。

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

1 - A function template can be overloaded either by (non-template) functions of its name or by (other) function templates of the same name. When a call to that name is written (explicitly, or implicitly using the operator notation), template argument deduction (14.8.2) and checking of any explicit template arguments (14.3) are performed for each function template to find the template argument values (if any) that can be used with that function template to instantiate a function template specialization that can be invoked with the call arguments. [...]

因此,声明 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 - Return type deduction for a function template with a placeholder in its declared type occurs when the definition is instantiated [...]

由于定义实例化发生在模板重载决议(14.7.1p3)之后,错误的模板iter<0>从未实例化; 14.8.3p5:

5 - Only the signature of a function template specialization is needed to enter the specialization in a set of candidate functions. Therefore only the function template declaration is needed to resolve a call for which a template specialization is a candidate.

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 的调用中.

关于c++ - 递归尾随返回类型的名称解析,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24036357/

相关文章:

c++ - 编译器无法推导出模板函数的类型?

C++11 VS2013类POD成员初始化

c++ - 为什么将 unsigned long 与负数进行比较会导致 false?

c++ - C++ 中的类私有(private)成员是否保证内存顺序?

c++ - 包括 std::forward 会产生错误,但它的遗漏会编译。为什么?

c++ - 如何将 std::find() 与自定义类的 vector 一起使用?

java - 在 java 类上生成 C++ 包装器的工具

c++ - 为什么 std::remove_if 创建这么多闭包?

c++ - static_cast 如何与虚拟继承一起使用?

python - 从C++函数与Python函数返回的值不一致,导致偏正态分布