模板元编程:1 到 n 的总和

Template metaprogramming: Sum of 1 to n

本文关键字:编程      更新时间:2023-10-16

有没有一种聪明的方法可以通过模板元编程计算连续数字的总和? 这将是例如 1 到 100 的非模板算法。

int i = 0;
for (int n = 1; n <= 100; n++)
i += n;
return i;

我想过使用可变参数添加函数并用参数列表填充它。但是我不确定如何创建参数列表。

// add function
template<typename T>
T add(T a) {
return a;
}
template<typename T, typename... Args>
T add(T a, Args... args) {
return a + add(args...); // a + a + a + .. + add()
}
template <int n>
struct SumOfNumbers {
static constexpr int value = n*(n+1)/2;
};

如果将代码放在constexpr函数中,则可以在编译时执行。

constexpr int sum(int x)
{
int i = 0;
for (int n = 1; n <= x; n++)
i += n;
return i;
}

如果你想在模板中使用它,你可以做:

template <int n>
struct sum_t {
static constexpr int value = sum(n);
};

虽然 Igor 的答案是手头问题的好答案,但以下方法更类似于 for 循环。

template <int n>
struct AddHelper { static const int value = n + AddHelper<n-1>::value; };
template <>
struct AddHelper<0> { static const int value = 0; };
template <int n>
int add() { return AddHelper<n>::value; }

简单的方法是使用constexpr.constexpr,尤其是在 C++14 中,允许您获取运行时代码并使其编译时。

在 C++11 中,你必须对递归变得愚蠢。 幸运的是,现在是 2017 年,在 C++14 发布多年后,所以您不必使用 C++11。 如果被迫,可以在问题constexpr中标记template实现。

然后

template<int...Is>
constexpr int add() { return add(0, Is...); }

如果您只需要在<>s 内传递参数,就可以解决问题。

有时,当人们想要一个通用解决方案时,他们会问这些问题,并且只为特定的解决方案选择add,却没有意识到它包含使问题更容易的功能。

因此,如果做不到这一点,在二进制添加上天真的左折叠或右折叠就可以了。 二进制左折或右折的缺点是它具有 O(n( 模板实例化深度和 O(n^2( 总模板名称长度。 这既限制了您可以添加的序列的长度,又使其非常慢且占用内存。

在 C++17 中,处理此问题的方法是使用内置的折叠操作。 如果要折叠的二进制操作不是内置操作之一,请使用运算符重载和包装器类型在执行语言内置折叠时强制调用它。 这是C++17,你的问题是C++11,所以我不会详细介绍。

在 C++11 中,你有一些任意对称的二进制操作想要折叠,你可以获取一个东西列表,并使用对数递归深度将其分解为二叉树。

这很棘手;基本思想是编写一个函数,将列表在索引处拆分为两个,然后附加前缀和后缀列表。

然后,在伪代码中,如果我们where索引,则只有在提到L0时才list={L0, Ls...}索引(否则它只是一些列表(。

split( where, before, list )
if where=0, answer is before, list
if where=1, answer is before+{L0}, {Ls...}
otherwise, is:
using tmp[2] = split((where+1)/2, before, list)
answer is split( where/2, tmp[0], tmp[1])

诀窍是我们做了很多实例化,但递归深度很短。

一旦我们有了split,我们就可以获取元素列表,构建一个快速平衡的二叉树,并将您的二元运算符应用于每对元素,并再次以对数深度求解树。

老实说,这比升级编译器更麻烦。


TL;DR:将编译器升级到 c++14 或 c++17,这很容易。

如果做不到这一点,请尝试并添加constexpr. 现在按原样使用,或向前使用

template<int...Is>
constexpr int add()

到它。

我想过使用可变参数添加函数并用参数列表填充它。但是我不确定如何创建参数列表。

首先,我建议您遵循Igor Tandetnik(+1(的基于高斯的解决方案。

第二:我不知道如何创建参数列表,但是如果你接受一个std::integer_sequence(所以参数列表固定编译时间(,从C++14开始你可以使用std::make_integer_sequence

在这种情况下,您的add()函数不需要递归,如以下示例所示。

#include <utility>
template <typename T, T ... Is>
constexpr T add (std::integer_sequence<T, Is...> const & a)
{
using unused = int[];
T sum {};
(void)unused { 0, ( sum += Is, 0 )... };
return sum;
}

int main ()
{
constexpr int sum1 { (100 * (100 + 1)) >> 1 }; // Gauss
constexpr int sum2 { add(std::make_integer_sequence<int, 100+1>{}) };
static_assert( sum1 == sum2, "!");
}

在C++17中,解决方案更加简单

template <typename T, T ... Is>
constexpr T add (std::integer_sequence<T, Is...> const & a)
{ return ( Is + ... ); }