模板元编程:1 到 n 的总和
Template metaprogramming: Sum of 1 to n
有没有一种聪明的方法可以通过模板元编程计算连续数字的总和? 这将是例如 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 + ... ); }
- 有一个打印语句的函数是一种糟糕的编程实践吗
- 我是C++编程的新手,这些代码之间有什么区别,我应该使用哪一个
- 模板元编程:如何将参数包组合成新的参数包
- Qt Q串行端口未编程设备未关闭
- 模板元编程 - 尝试实现维度分析
- 我是编程新手
- C++编程从外部文本文件定义数组大小
- 了解算法的性能差异(如果以不同的编程语言实现)
- 使用 Gtkmm 以编程方式选择 Gtk::TextView 中的文本
- 如何将可变参数模板转换为多个单个模板?(C++竞争编程调试模板)
- 使用命名空间正确编程
- C++编程:运算符重载中的引用如何工作?
- Arduino 模块化编程与全局和设置
- C++ 运算符修改/元编程策略,用于不那么冗长的语法
- 在没有管理员权限的情况下,在 c++ 中以编程方式将程序添加到启动
- 如何以编程方式将音频从任何录制设备路由到任何播放设备
- 试图修复一个错误,该错误不会让我开始编程其余部分
- C++模板编程设计问题 - 根据输入文件返回不同的类型
- Frank Luna 在他的书"使用 DirectX12 进行 3D 游戏编程"的介绍中盒子示例的问题
- 如何使用可变参数模板类使用模板元编程获得最大值