22

I'm trying to write a variadic template constexpr function which calculates sum of the template parameters given. Here's my code:

template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}

template<int First>
constexpr int f()
{
    return First;
}

int main()
{
    f<1, 2, 3>();
    return 0;
}

Unfortunately, it does not compile reporting an error message error C2668: 'f': ambiguous call to overloaded function while trying to resolve f<3,>() call.

I also tried to change my recursion base case to accept 0 template arguments instead of 1:

template<>
constexpr int f()
{
    return 0;
}

But this code also does not compile (message error C2912: explicit specialization 'int f(void)' is not a specialization of a function template).

I could extract first and second template arguments to make this compile and work, like this:

template<int First, int Second, int... Rest>
constexpr int f()
{
    return First + f<Second, Rest...>();
}

But this does not seem to be the best option. So, the question is: how to write this calculation in an elegant way?

UP: I also tried to write this as a single function:

template<int First, int... Rest>
constexpr int f()
{
    return sizeof...(Rest) == 0 ? First : (First + f<Rest...>());
}

And this also does not work: error C2672: 'f': no matching overloaded function found.

alexeykuzmin0
  • 6,344
  • 2
  • 28
  • 51
  • This is kind of redundant, isn't it? If it is a constexpr, the compiler is allowed to evaluate the expression at compiletime anyways, so why make it a template? why not define `constexpr int f(int a, ...)`? – example Aug 04 '16 at 19:32
  • @example Yes, for the sum such method works, but in my actual case the function is a bit more complicated and requires knowing the length of `Rest...` (something like convert to another base) which cannot be evaluated as a `constexpr` – alexeykuzmin0 Aug 04 '16 at 19:39

6 Answers6

21

Your base case was wrong. You need a case for the empty list, but as the compiler suggests, your second try was not a valid template specialization. One way to define a valid instantiation for zero arguments is to create an overload that accepts an empty list

template<class none = void>
constexpr int f()
{
    return 0;
}
template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}
int main()
{
    f<1, 2, 3>();
    return 0;
}

EDIT: for completeness sake also my first answer, that @alexeykuzmin0 fixed by adding the conditional:

template<int First=0, int... Rest>
constexpr int f()
{
    return sizeof...(Rest)==0 ? First : First + f<Rest...>();
}
example
  • 3,349
  • 1
  • 20
  • 29
13
template<int First, int... Rest>
constexpr int f()
{
    return First + f<Rest...>();
}

template<int First>
constexpr int f()
{
    return First;
}

int main()
{
    f<1, 2, 3>();
    return 0;
}

You get this error:

error C2668: 'f': ambiguous call to overloaded function while trying to resolve f<3,>() call.

This is because a variadic parameter pack can be given 0 arguments, so f<3> could work with template<int First, int... Rest> by "expanding" to template<3, >. However, you also have the specialization of template<int First>, so the compiler does not know which one to choose.

Explicitly stating the first and second template arguments is a completely valid and good solution to this problem.


When you try to change the base case to:

template <>
constexpr int f()
{
    return 0;
}

You have a problem because functions cannot be specialized in that way. Classes and structs can be, but not functions.


Solution #1: C++17 fold expression with constexpr

template <typename... Is>
constexpr int sum(Is... values) {
    return (0 + ... + values);
}

Solution #2: Use a constexpr function

constexpr int sum() {
    return 0;
}

template <typename I, typename... Is>
constexpr int sum(I first, Is... rest) {
    return first + sum(rest...);
}

Solution #3: Use Template Metaprogramming

template <int... Is>
struct sum;

template <>
struct sum<>
    : std::integral_constant<int, 0>
{};

template <int First, int... Rest>
struct sum<First, Rest...>
    : std::integral_constant<int,
        First + sum_impl<Rest...>::value>
{};
Justin
  • 24,288
  • 12
  • 92
  • 142
  • Thank you for the detailed explanation – alexeykuzmin0 Aug 04 '16 at 19:58
  • I've been trying to do this with strings, can't seem to constexpr succesfully using std::stringview, and also failing with the metaprogramming bit, am I being a dummy or is that just not possible like this? – Troyseph Mar 01 '20 at 12:52
10

I find it generally easier to move the code from template arguments to function arguments:

constexpr int sum() { return 0; }

template <class T, class... Ts>
constexpr int sum(T value, Ts... rest) {
    return value + sum(rest...);
}

If you really want them as template arguments, you can have your f just call sum by moving them down:

template <int... Is>
constexpr int f() {
    return sum(Is...);
}

It's constexpr, so just using ints is fine.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Yes, for the sum such method works, but my life is a bit more complicated. Closing as the question is formulated incorrectly. – alexeykuzmin0 Aug 04 '16 at 19:47
7

Just sum it up the usual way.

template<int... Args>
constexpr int f() {
   int sum = 0;
   for(int i : { Args... }) sum += i;
   return sum;
}
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • This was the first thing I tried. Unfortunately, my compiler (MSVC++ 2015) does not understand variables declarations in `constexpr` functions. – alexeykuzmin0 Aug 04 '16 at 19:29
  • @alexeykuzmin0 I see that you changed the C++14 tag. OK. – T.C. Aug 04 '16 at 19:30
  • Yes, I just realised that lack of this feature means that my compiler has no full support of C++14 – alexeykuzmin0 Aug 04 '16 at 19:31
  • 1
    @alexeykuzmin0 changing the question requirements in such a way that it invalidates posted answers isn't generally regarded as polite, because it makes it look like the person who posted the answer did so incorrectly. – jaggedSpire Aug 04 '16 at 19:38
  • 1
    @jaggedSpire I understand this and am very sorry. But letting the question be formulated incorrectly would not help getting the right answer. – alexeykuzmin0 Aug 04 '16 at 19:41
  • If the original motivation of the original question is to accomplish large parts of the evaluation already at build time, this approach has the downside that dynamic code (an iterative for loop) is generated. The other examples expand to an explicit sum of the correct length that is processed without a dynamic iteration and loop checks/jumps. /// For toy examples like summing integers, this appears a bit like overshoot, but I guess this question is a simple model for real-life problems. – HelpingHand Jul 16 '22 at 10:32
3

A more generic solution using std::initializer_list would be:

template <typename... V>                                                                                                                                                                                         
auto sum_all(V... v)
{
  using rettype = typename std::common_type_t<V...>;
  rettype result{};
  (void)std::initializer_list<int>{(result += v, 0)...};
  return result;
}
Patryk
  • 22,602
  • 44
  • 128
  • 244
0

It is quite similar to what @T.C suggested above:

#include<iostream>
#include<numeric>

template<int First, int... Rest>
constexpr int f()
{
    auto types = {Rest...};
    return std::accumulate(std::begin(types), std::end(types),0);   
}

int main()
{
    std::cout <<f<1, 2, 3>();
    return 0;
}
skypjack
  • 49,335
  • 19
  • 95
  • 187
Steephen
  • 14,645
  • 7
  • 40
  • 47