我正在使用C ++ 14中的constexpr函数进行实验。以下代码计算了阶乘按预期工作:
template <typename T>
constexpr auto fact(T a) {
if(a==1)
return 1;
return a*fact(a-1);
}
int main(void) {
static_assert(fact(3)==6, "fact doesn't work");
}
用clang编译如下:
> clang++ --version
clang version 3.5.0 (tags/RELEASE_350/final)
Target: x86_64-unknown-linux-gnu
Thread model: posix
> clang++ -std=c++14 constexpr.cpp
但是,当我更改fact
定义以使用三元?
运算符时:
template <typename T>
constexpr auto fact(T a) {
return a==1 ? 1 : a*fact(a-1);
}
我收到以下编译错误:
> clang++ -std=c++14 constexpr.cpp
constexpr.cpp:12:31: fatal error: recursive template instantiation exceeded maximum depth of
256
return a==T(1) ? T(1) : a*fact(a-1);
... snip ...
constexpr.cpp:16:19: note: in instantiation of function template specialization 'fact<int>'
requested here
static_assert(fact(3)==6, "fact doesn't work");
如果我明确说明返回类型T(而不是使用auto来推断返回类型),问题就解决了。
template <typename T>
constexpr T fact(T a) {
return a==1 ? 1 : a*fact(a-1);
}
如果删除模板参数,则会重复该模式(三元版本失败,if
版本有效)
// this works just fine
constexpr auto fact(int a) {
if(a==1)
return 1;
return a*fact(a-1);
}
虽然失败
constexpr auto fact(int a) {
return a==1 ? 1 : a*fact(a-1);
}
出现以下错误
> clang++ -std=c++14 constexpr.cpp
constexpr.cpp:16:25: error: function 'fact' with deduced return type cannot be used before it
is defined
return a==1 ? 1 : a*fact(a-1);
^
constexpr.cpp:15:16: note: 'fact' declared here
constexpr auto fact(int a) {
^
constexpr.cpp:20:26: error: invalid operands to binary expression ('void' and 'int')
static_assert(fact(3)==6, "fact doesn't work");
~~~~~~~^ ~
2 errors generated.
这里发生了什么?
答案 0 :(得分:11)
评估三元表达式的结果类型是common type of its second and third arguments。
通过让编译器推导出返回类型,可以强制它评估三元表达式的这两个参数。这意味着即使达到终止条件,递归也不会结束,因为当a==1
时,为了找出fact(0)
的返回类型,编译器必须继续评估对{{1}的进一步递归调用。随之而来的是无休止的递归。
通过声明返回类型,fact
时不需要评估fact(0)
,并且递归能够终止。
对于两个a==1
语句的情况,相关的标准条款是 -
(来自N4296)§7.1.6.4/ 9 [dcl.spec.auto]
如果具有包含占位符类型的声明返回类型的函数具有多个return语句,则将为每个返回语句推导出返回类型。如果推断的类型在每次扣除中不相同,则该程序格式不正确。
在您的示例中,在对return
的调用中,从第一个fact<int>(1)
语句推导出的返回类型为return
,因此第二个int
的返回类型为fact<int>(0)
除了return
之外,int
语句不能是任何内容。这意味着编译器不需要计算fact<int>(0)
的主体,并且递归可以终止。
实际上,如果您强制在第二个fact
语句中对return
的调用进行评估,例如通过更改第一个示例,以便T
是非类型模板论证
template <unsigned T>
constexpr auto fact() {
if(T==1)
return 1;
return T*fact<T-1>();
}
clang确实失败并显示错误
致命错误:递归模板实例化超过最大深度256