是否有一种方法可以使此C 14递归模板在C 17中短

Is there a way to make this C++14 recursive template shorter in C++17?

本文关键字:递归 中短 可以使 方法 一种 是否      更新时间:2023-10-16

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建议的 std::common_type a 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<<()成为非成员。