trailing return type using decltype with a variadic template function

Viewed 6892

I want to write a simple adder (for giggles) that adds up every argument and returns a sum with appropriate type. Currently, I've got this:

#include <iostream>
using namespace std;

template <class T>
T sum(const T& in)
{
   return in;
}

template <class T, class... P>
auto sum(const T& t, const P&... p) -> decltype(t + sum(p...))
{
   return t + sum(p...);
}

int main()
{
   cout << sum(5, 10.0, 22.2) << endl;
}

On GCC 4.5.1 this seems to work just fine for 2 arguments e.g. sum(2, 5.5) returns with 7.5. However, with more arguments than this, I get errors that sum() is simply not defined yet. If I declare sum() like this however:

template <class T, class P...>
T sum(const T& t, const P&... p);

Then it works for any number of arguments, but sum(2, 5.5) would return integer 7, which is not what I would expect. With more than two arguments I assume that decltype() would have to do some sort of recursion to be able to deduce the type of t + sum(p...). Is this legal C++0x? or does decltype() only work with non-variadic declarations? If that is the case, how would you write such a function?

7 Answers

I provide this improvement to the accepted answer. Just two structs

#include <utility>

template <typename P, typename... Ps>
struct sum_type {
    using type = decltype(std::declval<P>() + std::declval<typename sum_type<Ps...>::type>());
};

template <typename P>
struct sum_type<P> {
    using type = P;
};

Now just declare your functions as

template <class T>
auto sum(const T& in) -> T
{
   return in;
}

template <class P, class ...Ps>
auto sum(const P& t, const Ps&... ps) -> typename sum_type<P, Ps...>::type
{
   return t + sum(ps...);
}

With this, your test code now works

std::cout << sum(5, 10.0, 22.2, 33, 21.3, 55) << std::endl;

146.5

Right way to do:

#include <utility>

template <typename... Args>
struct sum_type;

template <typename... Args>
using sum_type_t = typename sum_type<Args...>::type;

template <typename A>
struct sum_type<A> {
    using type = decltype( std::declval<A>() );
};

template <typename A, typename B>
struct sum_type<A, B> {
    using type = decltype( std::declval<A>() + std::declval<B>() );
};

template <typename A, typename B, typename... Args>
struct sum_type<A, B, Args...> {
    using type = sum_type_t< sum_type_t<A, B>, Args... >;
};

template <typename A>
sum_type_t<A> sum(A &&a)
{
    return (std::forward<A>(a));
}

template <typename A, typename B>
sum_type_t<A, B> sum(A &&a, B &&b)
{
    return (std::forward<A>(a) + std::forward<B>(b));
}

template <typename A, typename B, typename... C>
sum_type_t<A, B, C...> sum(A &&a, B &&b, C &&...args)
{
    return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(args)... );
}

https://coliru.stacked-crooked.com/a/a5a0e8019e40b8ba

This completely preserves resulting type of operations (even r-value referenceness). The order of operations is natural: (((a+b)+c)+d).

For C++17:

template <class... P>
auto sum(const P... p){
    return (p + ...);
}

int main()
{
    std::cout << sum(1, 3.5, 5) << std::endl;
    return EXIT_SUCCESS;
}

Read about folding expressions.

Related