没有模板特化的模板阶乘函数
Template factorial function without template specialization
我不理解以下行为。
以下旨在在编译时计算阶乘的代码甚至无法编译:
#include <iostream>
using namespace std;
template<int N>
int f() {
if (N == 1) return 1; // we exit the recursion at 1 instead of 0
return N*f<N-1>();
}
int main() {
cout << f<5>() << endl;
return 0;
}
并抛出以下错误:
...$ g++ factorial.cpp && ./a.out
factorial.cpp: In instantiation of ‘int f() [with int N = -894]’:
factorial.cpp:7:18: recursively required from ‘int f() [with int N = 4]’
factorial.cpp:7:18: required from ‘int f() [with int N = 5]’
factorial.cpp:15:16: required from here
factorial.cpp:7:18: fatal error: template instantiation depth exceeds maximum of 900 (use ‘-ftemplate-depth=’ to increase the maximum)
7 | return N*f<N-1>();
| ~~~~~~^~
compilation terminated.
然而,在为 N == 0
添加专业化后(上面的模板甚至达不到),
template<>
int f<0>() {
cout << "Hello, I'm the specialization.\n";
return 1;
}
代码编译并给出正确的输出,即使从未使用过专业化:
...$ g++ factorial.cpp && ./a.out
120
这里的问题是您的 if 语句是 运行 时间结构。当你有
int f() {
if (N == 1) return 1; // we exit the recursion at 1 instead of 0
return N*f<N-1>();
}
f<N-1>
被实例化,因为它可能被调用。即使 if 条件会阻止它调用 f<0>
,编译器仍然必须实例化它,因为它是函数的一部分。这意味着它实例化 f<4>
,它实例化 f<3>
,它实例化 f<2>
,如此循环往复。
Pre C++17 的方法是使用 0
的特化来打断链条。从使用 constexpr if 的 C++17 开始,不再需要它。使用
int f() {
if constexpr (N == 1) return 1; // we exit the recursion at 1 instead of 0
else return N*f<N-1>();
}
保证 return N*f<N-1>();
甚至不会存在于 1
情况下,因此您不会继续陷入实例化的兔子洞。
问题是 f<N>()
总是 实例化 f<N-1>()
无论分支是否被采用。除非正确终止,否则会在编译时创建无限递归(即它会尝试实例化 F<0>
,然后 f<-1>
,然后 f<-2>
等等)。显然,您应该以某种方式终止该递归。
除了 NathanOliver 建议的 constexpr
解决方案和专业化之外,您可以显式终止递归:
template <int N>
inline int f()
{
if (N <= 1)
return 1;
return N * f<(N <= 1) ? N : N - 1>();
}
注意,这个解决方案很差(相同的终止条件必须重复两次),我写这个答案只是为了表明总是有更多的方法来解决这个问题:-)
我不理解以下行为。
以下旨在在编译时计算阶乘的代码甚至无法编译:
#include <iostream>
using namespace std;
template<int N>
int f() {
if (N == 1) return 1; // we exit the recursion at 1 instead of 0
return N*f<N-1>();
}
int main() {
cout << f<5>() << endl;
return 0;
}
并抛出以下错误:
...$ g++ factorial.cpp && ./a.out
factorial.cpp: In instantiation of ‘int f() [with int N = -894]’:
factorial.cpp:7:18: recursively required from ‘int f() [with int N = 4]’
factorial.cpp:7:18: required from ‘int f() [with int N = 5]’
factorial.cpp:15:16: required from here
factorial.cpp:7:18: fatal error: template instantiation depth exceeds maximum of 900 (use ‘-ftemplate-depth=’ to increase the maximum)
7 | return N*f<N-1>();
| ~~~~~~^~
compilation terminated.
然而,在为 N == 0
添加专业化后(上面的模板甚至达不到),
template<>
int f<0>() {
cout << "Hello, I'm the specialization.\n";
return 1;
}
代码编译并给出正确的输出,即使从未使用过专业化:
...$ g++ factorial.cpp && ./a.out
120
这里的问题是您的 if 语句是 运行 时间结构。当你有
int f() {
if (N == 1) return 1; // we exit the recursion at 1 instead of 0
return N*f<N-1>();
}
f<N-1>
被实例化,因为它可能被调用。即使 if 条件会阻止它调用 f<0>
,编译器仍然必须实例化它,因为它是函数的一部分。这意味着它实例化 f<4>
,它实例化 f<3>
,它实例化 f<2>
,如此循环往复。
Pre C++17 的方法是使用 0
的特化来打断链条。从使用 constexpr if 的 C++17 开始,不再需要它。使用
int f() {
if constexpr (N == 1) return 1; // we exit the recursion at 1 instead of 0
else return N*f<N-1>();
}
保证 return N*f<N-1>();
甚至不会存在于 1
情况下,因此您不会继续陷入实例化的兔子洞。
问题是 f<N>()
总是 实例化 f<N-1>()
无论分支是否被采用。除非正确终止,否则会在编译时创建无限递归(即它会尝试实例化 F<0>
,然后 f<-1>
,然后 f<-2>
等等)。显然,您应该以某种方式终止该递归。
除了 NathanOliver 建议的 constexpr
解决方案和专业化之外,您可以显式终止递归:
template <int N>
inline int f()
{
if (N <= 1)
return 1;
return N * f<(N <= 1) ? N : N - 1>();
}
注意,这个解决方案很差(相同的终止条件必须重复两次),我写这个答案只是为了表明总是有更多的方法来解决这个问题:-)