在 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 求值。