This poly_eval function will compute the result of evaluating a polynomial with a particular set of coefficients at a particular value of x. For example, poly_eval(5, 1, -2, -1) computes x^2 - 2x - 1 with x = 5. It's all constexpr so if you give it constants it will compute the answer at compile time.
It currently uses recursive templates to build the polynomial evaluation expression at compile time and relies on C++14 to be constexpr. I was wondering if anybody could think of a good way to remove the recursive template, perhaps using C++17. The code that exercises the template uses the __uint128_t type from clang and gcc.
#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);
}
Also, am I doing anything wrong here?
-------- Comments on Answers --------
There are two excellent answers down below. One is clear and terse, but may not handle certain situations involving complex types (expression trees, matrices, etc..) well, though it does a fair job. It also relies on the somewhat obscure , operator.
The other is less terse, but still much clearer than my original recursive template, and it handles types just as well. It expands out to 'cn + x * (cn-1 + x * (cn-2 ...' whereas my recursive version expands out to cn + x * cn-1 + x * x * cn-2 .... For most reasonable types they should be equivalent, and the answer can easily be modified to expand out to what my recursive one expands to.
I picked the first answer because it was 1st and its terseness is more within the spirit of my original question. But, if I were to choose a version for production, I'd choose the second.