是否有一种方法可以使此C 14递归模板在C 17中短
Is there a way to make this C++14 recursive template shorter in C++17?
此poly_eval
函数将计算以特定的系数为x的特定系数评估多项式的结果。例如, poly_eval(5, 1, -2, -1)
计算x^2-2x -1使用x =5。这是constexpr
,因此,如果您给它常数,它将在编译时计算答案。
当前,它使用递归模板在编译时构建多项式评估表达式,并依赖于C 14为constexpr
。我想知道是否有人可以想到删除递归模板的好方法,也许是使用C 17。练习模板的代码使用Clang和GCC的__uint128_t
类型。
#include <type_traits>
#include <tuple>
template <typename X_t, typename Coeff_1_T>
constexpr auto poly_eval_accum(const X_t &x, const Coeff_1_T &c1)
{
return ::std::pair<X_t, Coeff_1_T>(x, c1);
}
template <typename X_t, typename Coeff_1_T, typename... Coeff_TList>
constexpr auto poly_eval_accum(const X_t &x, const Coeff_1_T &c1, const Coeff_TList &... coeffs)
{
const auto &tmp_result = poly_eval_accum(x, coeffs...);
auto saved = tmp_result.second + tmp_result.first * c1;
return ::std::pair<X_t, decltype(saved)>(tmp_result.first * x, saved);
}
template <typename X_t, typename... Coeff_TList>
constexpr auto poly_eval(const X_t &x, const Coeff_TList &... coeffs)
{
static_assert(sizeof...(coeffs) > 0,
"Must have at least one coefficient.");
return poly_eval_accum(x, coeffs...).second;
}
// This is just a test function to exercise the template.
__uint128_t multiply_lots(__uint128_t num, __uint128_t n2)
{
const __uint128_t cf = 5;
return poly_eval(cf, num, n2, 10);
}
// This is just a test function to exercise the template to make sure
// it computes the result at compile time.
__uint128_t eval_const()
{
return poly_eval(5, 1, -2, 1);
}
另外,我在这里做错了吗?
---------关于答案的评论--------
下面有两个出色的答案。一个清晰且简短,但可能无法处理涉及复杂类型(表达树,矩阵等)的某些情况,尽管它有效。它还依赖于某种晦涩的操作员。
另一个比我的原始递归模板更清晰,但仍然可以清楚得多,并且也可以处理类型。它扩展到'cn x *(cn-1 x *(cn-2 ...',而我的递归版本都会扩展到 cn + x * cn-1 + x * x * cn-2 ...
。对于大多数合理类型,它们应该等效,并且可以轻松地修改答案为扩展到我的递归的扩展。
我选择了第一个答案,因为它是第一个,而它的简短更属于我最初的问题的精神。但是,如果我要选择生产版本,我会选择第二个。
使用逗号运算符的功率(显然C 17折叠),我想您可以按照以下方式编写poly_eval()
template <typename X_t, typename C_t, typename ... Cs_t>
constexpr auto poly_eval (X_t const & x, C_t a, Cs_t const & ... cs)
{
( (a *= x, a += cs), ..., (void)0 );
return a;
}
拖走poly_eval_accum()
。
观察第一个系数(如果已解释),因此您还可以删除static_assert()
并通过复制传递,并成为累加器。
- 编辑 -
添加了一个替代版本,以使用op建议的 a std::common_type
decltype()
解决返回类型的问题;在此版本中,a
再次是常数参考。
template <typename X_t, typename C_t, typename ... Cs_t>
constexpr auto poly_eval (X_t const & x, C_t const & c1, Cs_t const & ... cs)
{
decltype(((x * c1) + ... + (x * cs))) ret { c1 };
( (ret *= x, ret += cs), ..., (void)0 );
return ret;
}
- 编辑2 -
奖金答案:可能还可以使用逗号操作员的功率(再次)避免C 14中的递归,并初始化未使用的C风格整数数组
template <typename X_t, typename C_t, typename ... Cs_t>
constexpr auto poly_eval (X_t const & x, C_t const & a, Cs_t const & ... cs)
{
using unused = int[];
std::common_type_t<decltype(x * a), decltype(x * cs)...> ret { a };
(void)unused { 0, (ret *= x, ret += cs)... };
return ret;
}
上面提供了一个很好的答案,但是它需要一种共同的回报类型,因此,如果您构建了编译时表达式树,则将不起作用。
我们需要的是某种方法,即具有在评估点x
上与值乘以值并在每次迭代时添加系数的倍数,以最终以:(((c0) * x + c1) * x + c2) * x + c3
之类的表达式。这是(我认为)直接使用折叠表达式的(我认为),但是我们可以定义一种特殊的类型,使二进制操作员超载并执行必要的计算。
template<class M, class T>
struct MultiplyAdder
{
M mul;
T acc;
constexpr MultiplyAdder(M m, T a) : mul(m), acc(a) { }
};
template<class M, class T, class U>
constexpr auto operator<<(const MultiplyAdder<M,T>& ma, const U& u)
{
return MultiplyAdder(ma.mul, ma.acc * ma.mul + u);
}
template <typename X_t, typename C_t, typename... Coeff_TList>
constexpr auto poly_eval(const X_t &x, const C_t &a, const Coeff_TList &... coeffs)
{
return (MultiplyAdder(x, a) << ... << coeffs).acc;
}
作为奖励,此解决方案还勾选了C 17的"自动类模板参数扣除"框;)
编辑:糟糕,参数扣除在MultiplyAdder<>::operator<<()
内没有工作,因为MultiplyAdder
是指其自己的模板ID,而不是其模板名称。我添加了一个名称空间规范符,但不幸的是,它取决于其自己的名称空间。必须有一种方法来参考其实际的模板名称,但是我不能不求助于模板别名。
edit2:通过使operator<<()
成为非成员。
- 通过递归进行因子分解
- 递归函数计算序列中的平方和(并输出过程)
- 使用递归的数组的最小值.这是怎么回事
- 递归列出所有目录中的C++与Python与Ruby的性能
- 递归计数给定目录的文件和所有目录
- 如何在BST的这个简单递归实现中消除警告
- C++:正在检查LinkedList中的回文-递归方法-错误
- 递归模板化函数不能分配给具有常量限定类型"const tt &"的变量"state"
- 递归无序映射
- TSP递归解的迭代形式
- 如何在Elixir中调用递归函数并行
- 返回递归调用和仅递归调用的区别
- 数组元素打印的递归方法
- 使用递归时获取变量的奇怪值
- 如何在C++中递归地按相反顺序打印集合
- 到连接组件算法的问题(递归)
- 如何使用递归打印修改后的星号三角形图案
- 使用递归模板动态分配的多维数组
- 递归函数有效,但无法记忆
- 是否有一种方法可以使此C 14递归模板在C 17中短