7

The code below works for the: goal for the left associative sum operation: sum(1,2,3,4);

However, it won't work correctly for sum(1,2,3,4,5) or sum(1,2,3,4,5,...). Anything with more than 4 arguments gives the error:

error: no matching function for call to sum(int, int, int, int, int)

=================================

template <typename T>
T sum(const T& v) {
return v;
}

template <typename T1, typename T2>
auto sum(const T1& v1, const T2& v2) -> decltype( v1 + v2) {
return v1 + v2;
}

template <typename T1, typename T2, typename... Ts>
auto sum(const T1& v1, const T2& v2, const Ts&... rest) -> decltype( v1 + v2 +      sum(rest...) ) {
return v1 + v2 + sum(rest... );
}

int main() {
    cout << sum(1,2,3,4); //works correctly
    //cout << sum(1,2,3,4,5); //compile error

}
alper
  • 2,919
  • 9
  • 53
  • 102
  • My guess is that it can't evauluate the sum using varidic template recursivly before it is decleared ones. I've no clue though how to solve it. Impossible? – Xale May 21 '13 at 07:07
  • I can reproduce that issue on gcc 4.7.2: http://ideone.com/6X4f2b - seems that gcc refuses to call variadic templates with auto/decltype return types recursively. – Arne Mertz May 21 '13 at 07:08
  • 1
    See this [link][1]. Close to identical question. [1]: http://stackoverflow.com/questions/3744400/trailing-return-type-using-decltype-with-a-variadic-template-function – Xale May 21 '13 at 07:20
  • can be easily fixed with template class, instead of template function. I think it is right that it can't find proper function, but I am not sure – BЈовић May 21 '13 at 08:34

2 Answers2

5

That seems to be a bug in GCC, when working with variadic templates, auto return types and recursive reference to the same variadic template in the trailing return type.

C++11 - only right associative

It is solvable, through good old template meta programming:

//first a metafunction to calculate the result type of sum(Ts...)
template <typename...> struct SumTs;
template <typename T1> struct SumTs<T1> { typedef T1 type; };
template <typename T1, typename... Ts>
struct SumTs<T1, Ts...>
{
  typedef typename SumTs<Ts...>::type rhs_t;
  typedef decltype(std::declval<T1>() + std::declval<rhs_t>()) type;
};

//now the sum function
template <typename T>
T sum(const T& v) {
  return v;
}

template <typename T1, typename... Ts>
auto sum(const T1& v1, const Ts&... rest) 
  -> typename SumTs<T1,Ts...>::type //instead of the decltype
{
  return v1 + sum(rest... );
}

#include <iostream>
using std::cout;

int main() {
  cout << sum(1,2,3,4,5);    
}

PS: to be even more generic, the whole thing could be pimped with "universal references" and std::forward.

C++17 fold expressions

In C++17, the problem can be solved in basically one line:

template<typename T, typename... Ts>
constexpr auto sum(T&& t, Ts&&... ts) 
{
  return (std::forward<T>(t) + ... + std::forward<Ts>(ts));
}
``
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • 5
    `std::declval()` is the correct stand-in for `T()` inside of decltype. – Xeo May 21 '13 at 07:30
  • is there have to be "auto" keyword in front of sum(const T1& v1, const Ts&... rest) – alper May 21 '13 at 07:47
  • would it be possible to make this code work for the also Objects as arguments or only does it restrict with primitive type variables?/ http://stackoverflow.com/questions/16665531/variadic-templated-object-multiplication – alper May 21 '13 at 09:04
  • Doesn't this template produce a *right*-associative sum? `sum(1,2,3,4,5) --> (1+(2+(3+(4+5))))` ? A left-associative sum would be this: `((((1+2)+3)+4)+5)` This of course doesn't matter for `int`s but `T` could be a class with the `+` operator overloaded, and the two expressions might not be equal. – drwatsoncode Feb 19 '19 at 02:49
  • @ricovox it does. Making it left-associative would be a real hassle. These days, I would use C++17's fold expressions where we can chose the associativity. – Arne Mertz Feb 19 '19 at 08:46
2

The function need additional check:

#include <type_traits>

template <typename T>
T sum(T v) 
{
    static_assert(std::is_arithmetic<std::remove_reference<decltype(v)>::type>::value, 
    "type should be arithmetic");
    return v;
}

and it's better pass by value.

otherwise we can get strange result:

int main() {
std::cout << sum(1,"Hello World");


return 0;
}