【问题标题】:Is there a way to make this C++14 recursive template shorter in C++17?有没有办法让这个 C++14 递归模板在 C++17 中更短?
【发布时间】:2017-11-23 00:31:29
【问题描述】:

这个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);
}

另外,我在这里做错了吗?

-------- 对答案的评论--------

下面有两个很好的答案。一个是clear and terse, but may not handle certain situations involving complex types (expression trees, matrices, etc..) well,尽管它做得很好。它还依赖于有点晦涩的 , 运算符。

另一个不太简洁,但仍然是much clearer than my original recursive template, and it handles types just as well。它扩展为 'cn + x * (cn-1 + x * (cn-2 ...'修改为展开到我的递归展开到的内容。

我选择了第一个答案,因为它是第一个,而且它的简洁性更符合我最初问题的精神。但是,如果我要为生产选择一个版本,我会选择第二个。

【问题讨论】:

  • 最好包含该函数应该做什么的文字描述。
  • @JohnZwinck - 我添加了一段关于此的内容。
  • 如果您假设可以将系数强制转换为廉价复制的通用类型,您可以将参数包粉碎成一个数组并使用循环和索引等熟悉的工具。

标签: c++ c++14 c++17


【解决方案1】:

使用逗号运算符(显然还有 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()并通过副本传递,并成为累加器。

-- 编辑--

添加了一个替代版本来解决返回类型的问题,使用表达式的 std::common_type 一个 decltype(),正如 OP 建议的那样;在这个版本中,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;
 }

【讨论】:

  • 哦,这很漂亮。我忘记了,但我想, 运算符是一个序列点?虽然我知道 C++17 中的求值规则变得更加强大,但我认为序列点的想法仍然值得尊重,对吧?
  • @Omnifarious - 据我所知,逗号 operator 是一个序列点;逗号的序列点问题是用于分隔调用函数的参数时;在这种情况下未定义评估顺序,但在这种情况下,逗号是分隔符,而不是运算符。
  • @max66 但是如果sizeof...(cs) == 0,您的代码是否正确?似乎第一个系数乘以x。但是当你只有 1 个系数时,结果应该就是那个系数。相反,累加器应该从零开始,并且所有系数(包括第一个系数)都应该在cs 中传递。
  • @oisyn - 不,不乘以x,因为sizeof...(cs) == 0;折叠表达式变为( (void)0 );
  • @Omnifarious - 我明白了...是的,使用 decltype() 的产品更好(修改)但我理解问题:对于基本的算术类型,这应该有效,但不能保证适用于复杂的类型。
【解决方案2】:

上面提供了一个很好的答案,但它需要一个通用的返回类型,因此如果你正在构建一个编译时表达式树,它将无法工作。

我们需要某种方式来获得一个折叠表达式,它既可以与评估点 x 处的值相乘,又在每次迭代时添加一个系数,以便最终得到如下表达式:@987654322 @。这(我认为)直接使用折叠表达式是不可能的,但我们可以定义一种特殊的类型来重载二元运算符并进行必要的计算。

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&lt;&gt;::operator&lt;&lt;() 中不起作用,因为 MultiplyAdder 引用了它自己的模板 ID,而不是它的模板名称。我添加了一个命名空间说明符,但不幸的是它依赖于它自己的命名空间。必须有一种方法可以引用它的实际模板名称,但如果不求助于模板别名,我想不出任何方法。

Edit2:通过将operator&lt;&lt;()设为非成员来修复它。

【讨论】:

  • 这比我的原始代码更容易理解。它比我的@max66 代码版本更好地处理类型。还是比他的多。但它处理类型的事实是,恕我直言,足以弥补它。我认为它处理类型甚至比我的修改版本更好。
  • 是的:这对返回类型的问题进行了严格的处理。
  • 非常感谢。这很有趣,也很有教育意义。我很想转而接受你的回答,但我决定只是指着它并在我的回答中赞美它。 :-) 很高兴你能发现扣除错误。我应该注意到这一点。 :-)
猜你喜欢
  • 2020-07-29
  • 1970-01-01
  • 2019-03-23
  • 2019-07-04
  • 1970-01-01
  • 1970-01-01
  • 2020-06-25
  • 2019-07-08
  • 1970-01-01
相关资源
最近更新 更多