Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

variadic templates sum operation left associative

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

}
like image 911
alper Avatar asked May 21 '13 06:05

alper


2 Answers

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));
}
``
like image 197
Arne Mertz Avatar answered Oct 12 '22 14:10

Arne Mertz


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;
}
like image 32
Alexei Valyaev Avatar answered Oct 12 '22 13:10

Alexei Valyaev