在 C++ 中进行 N 阶乘编译时间的 3 种不同/相同方法
3 different / same ways of doing N-factorial compile time in C++
我正在尝试使用模板元编程、constexpr 和 if constexpr,并提出了 3 种不同的方法来执行 N 递归/N 阶乘运算。
所有这三个例子都是我在 SO 上或通过网络搜索找到的一些 - 然后对其进行了修改,所以它们也是如此
第一个示例使用模板元编程:示例 1
template<int N>
struct NGenerator
{
static const int result = N + NGenerator<N-1>::result;
};
template<>
struct NGenerator<0>
{
static const int result = 1;
};
static int example1 = NGenerator<5>::result;
第二个仍在使用模板,但我在以下内容中添加了一个 constexpr:示例 2
template<int N>
constexpr int example2()
{
return N + example2<N - 1>();
}
template<>
constexpr int example2<0>()
{
return 1;
}
static int ex2 = example2<5>();
第三个,是我删除模板并"only"使用constexpr的地方:示例3
constexpr int generator(int n)
{
return (n <= 1) ? 1 : n + generator(n - 1);
}
static int ex3 = generator(5);
在我看来,这三个都做同样的事情——当输入数字是一个编译时间常量时。这三个都是递归的,这三个都在编译时工作。
我的问题是 - 这三者有什么区别?哪一个最可取?
最后 - 我想实施 "if constexpr" 但没能做到,所以我的解决方法是 "if-statement"在示例 3 中——这不是真正的 if 语句,但我能得到的最接近编译时的 if——如果它在任何方面都相同,我不确定。
对于像这样的玩具示例,不会有太大区别。对于更复杂的示例,递归模板实例化的内存成本实际上与所有模板实例化名称(包括参数)的长度总和成正比。这个真的很容易炸。
根据我的经验 constexpr
函数往往编译得更快。
编译器可能会记住所有 3 个;但是 constexpr 非模板函数会产生更少的符号(噪声)。
比所有这些更好的是带有循环的 constexpr 函数。
编译器可以在运行时自由执行大部分选项,因为您没有坚持编译时评估。将 static int
替换为 constexpr int
.
示例1保证在编译时完成。
constexpr
函数(example2 和 example3)可以在非 constexpr 上下文中调用,因此可以在运行时计算(编译器可能仍会优化它以在编译时使用 as-if 规则计算它)。
我会做类似的事情:
constexpr std::size_t factorial(std::size_t n)
{
std::size_t res = 1;
for (std::size_t i = 1; i < n; ++i) {
res *= i;
}
return res;
}
template <std::size_t N>
static constexpr std::size_t Factorial = factorial(5);
static std::size_t ex4 = Factorial<5>;
C++20 添加 consteval
以仅允许 constexpr 求值。
我正在尝试使用模板元编程、constexpr 和 if constexpr,并提出了 3 种不同的方法来执行 N 递归/N 阶乘运算。
所有这三个例子都是我在 SO 上或通过网络搜索找到的一些 - 然后对其进行了修改,所以它们也是如此
第一个示例使用模板元编程:示例 1
template<int N>
struct NGenerator
{
static const int result = N + NGenerator<N-1>::result;
};
template<>
struct NGenerator<0>
{
static const int result = 1;
};
static int example1 = NGenerator<5>::result;
第二个仍在使用模板,但我在以下内容中添加了一个 constexpr:示例 2
template<int N>
constexpr int example2()
{
return N + example2<N - 1>();
}
template<>
constexpr int example2<0>()
{
return 1;
}
static int ex2 = example2<5>();
第三个,是我删除模板并"only"使用constexpr的地方:示例3
constexpr int generator(int n)
{
return (n <= 1) ? 1 : n + generator(n - 1);
}
static int ex3 = generator(5);
在我看来,这三个都做同样的事情——当输入数字是一个编译时间常量时。这三个都是递归的,这三个都在编译时工作。
我的问题是 - 这三者有什么区别?哪一个最可取?
最后 - 我想实施 "if constexpr" 但没能做到,所以我的解决方法是 "if-statement"在示例 3 中——这不是真正的 if 语句,但我能得到的最接近编译时的 if——如果它在任何方面都相同,我不确定。
对于像这样的玩具示例,不会有太大区别。对于更复杂的示例,递归模板实例化的内存成本实际上与所有模板实例化名称(包括参数)的长度总和成正比。这个真的很容易炸。
根据我的经验 constexpr
函数往往编译得更快。
编译器可能会记住所有 3 个;但是 constexpr 非模板函数会产生更少的符号(噪声)。
比所有这些更好的是带有循环的 constexpr 函数。
编译器可以在运行时自由执行大部分选项,因为您没有坚持编译时评估。将 static int
替换为 constexpr int
.
示例1保证在编译时完成。
constexpr
函数(example2 和 example3)可以在非 constexpr 上下文中调用,因此可以在运行时计算(编译器可能仍会优化它以在编译时使用 as-if 规则计算它)。
我会做类似的事情:
constexpr std::size_t factorial(std::size_t n)
{
std::size_t res = 1;
for (std::size_t i = 1; i < n; ++i) {
res *= i;
}
return res;
}
template <std::size_t N>
static constexpr std::size_t Factorial = factorial(5);
static std::size_t ex4 = Factorial<5>;
C++20 添加 consteval
以仅允许 constexpr 求值。