39

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?

Pubby
  • 51,882
  • 13
  • 139
  • 180
Maister
  • 4,978
  • 1
  • 31
  • 34
  • 1
    This is an interesting problem. Maybe you should ask in the Usenet group comp.std.c++ whether this kind of "recursive call" in `->decltype(expr)` is supposed to work or not. – sellibitze Sep 19 '10 at 06:39
  • 3
    It's not supposed to work by the current wording. The point of declaration of functions/variables etc.. is after their declarator. Thus, `sum` in the late specified return type cannot find the `sum` template being defined. – Johannes Schaub - litb Sep 19 '10 at 08:36
  • @Johannes: But isn't lookup simply delayed (until the 2nd phase) due to the expression's dependence on template parameters? – sellibitze Sep 19 '10 at 12:03
  • 1
    @sellibitze that's a good point, but it will depend on the types of the template parameters, because only argument dependent lookup is done at the instantiation context. If they are `int` and `double` like here, the function template won't be found. If there is a globally declared class among the arguments, the global `sum` will be found. So this is rather "random" when it finds the "sum", it doesn't work in general. – Johannes Schaub - litb Sep 19 '10 at 12:13
  • You forgot to make sum handle rvalue references correctly. – Puppy Sep 19 '10 at 12:23
  • @DeadMG, I don't see a problem here w.r.t. rvalues. Good old references-to-const handle rvalues just fine. – sellibitze Sep 19 '10 at 12:30
  • @sellibitze: Not if you don't forward them. Then, you will invoke the copy constructor and other lvalue semantics, even though in C++0x you could invoke move semantics and save a bunch of performance. Imagine if his sum were invoked on strings. Then he will waste a ton of performance with lvalues. – Puppy Sep 19 '10 at 12:45
  • 2
    @DeagMG: Point taken. But this is a little bit outside the scope of the question. – sellibitze Sep 19 '10 at 13:11
  • http://gcc.gnu.org/bugzilla/show_bug.cgi?id=44175 – Alex B May 12 '11 at 11:49

7 Answers7

23

I think the problem is that the variadic function template is only considered declared after you specified its return type so that sum in decltype can never refer to the variadic function template itself. But I'm not sure whether this is a GCC bug or C++0x simply doesn't allow this. My guess is that C++0x doesn't allow a "recursive" call in the ->decltype(expr) part.

As a workaround we can avoid this "recursive" call in ->decltype(expr) with a custom traits class:

#include <iostream>
#include <type_traits>
using namespace std;

template<class T> typename std::add_rvalue_reference<T>::type val();

template<class T> struct id{typedef T type;};

template<class T, class... P> struct sum_type;
template<class T> struct sum_type<T> : id<T> {};
template<class T, class U, class... P> struct sum_type<T,U,P...>
: sum_type< decltype( val<const T&>() + val<const U&>() ), P... > {};

This way, we can replace decltype in your program with typename sum_type<T,P...>::type and it will compile.

Edit: Since this actually returns decltype((a+b)+c) instead of decltype(a+(b+c)) which would be closer to how you use addition, you could replace the last specialization with this:

template<class T, class U, class... P> struct sum_type<T,U,P...>
: id<decltype(
      val<T>()
    + val<typename sum_type<U,P...>::type>()
)>{};
sellibitze
  • 27,611
  • 3
  • 75
  • 95
  • Indeed this works. I don't quite understand the template struct sum_type; though. Will it simply use the template version? – Maister Sep 19 '10 at 11:31
  • @Maister, The first specialization is for one argument and the second specialization is for at least two arguments (P might be an empty parameter pack). But Tomaka17's approach seems to work as well. There's one slight difference, though. My version gives you decltype((a+b)+c) while Tomaka17's version gives you decltype(a+(b+c)). In case you work with weird user-defined types this might make a difference. – sellibitze Sep 19 '10 at 11:42
  • I see, let's see if I got that right. So each time sum_type is instantiated, template sum_type; is used, but since there are specializations sum_type and sum_type, those specializations will be used instead, and thus there is no need to actually define the body of template struct sum_type; ? – Maister Sep 19 '10 at 11:55
  • 4
    Wasn't `decltype` actually meant to replace unnatural constructs like this? I really hope it's just a GCC bug, though I'm using 4.5.3 and it's still there. – Alex B May 12 '11 at 11:25
  • 2
    I already commented in Tomaka17's solution, i think for your solution the same problem exists, replacing `decltype( val() + val() )` with `decltype( std::declval() + std::declval() )` should solve the problem – Marti Nito Dec 10 '14 at 20:32
8

C++14's solution:

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

Return type is deducted automatically.

See it in online compiler

Or even better if you want to support different types of references:

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

See it in online compiler

If you need a natural order of summation (that is (((a+b)+c)+d) instead of (a+(b+(c+d)))), then the solution is more complex:

template <class A>
decltype(auto) sum(A &&a)
{
    return std::forward<A>(a);
}

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

template <class A, class B, class... C>
decltype(auto) sum(A &&a, B &&b, C &&...c)
{
    return sum( sum(std::forward<A>(a), std::forward<B>(b)), std::forward<C>(c)... );
}

See it in online compiler

anton_rh
  • 8,226
  • 7
  • 45
  • 73
GingerPlusPlus
  • 5,336
  • 1
  • 29
  • 52
  • 1
    That is incorrect. You should either put a trailing return type here (since C++11), or replace `auto` with `decltype(auto)` (since C++14). The first method may be more verbose but is better for various reasons. – KeyC0de Sep 23 '19 at 20:41
  • @Nikos, I updated the answer to use decltype(auto). – anton_rh Sep 10 '20 at 03:11
8

Apparently you can't use decltype in a recursive manner (at least for the moment, maybe they'll fix it)

You can use a template structure to determine the type of the sum

It looks ugly but it works

#include <iostream>
using namespace std;


template<typename... T>
struct TypeOfSum;

template<typename T>
struct TypeOfSum<T> {
    typedef T       type;
};

template<typename T, typename... P>
struct TypeOfSum<T,P...> {
    typedef decltype(T() + typename TypeOfSum<P...>::type())        type;
};



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

template <class T, class... P>
typename TypeOfSum<T,P...>::type sum(const T& t, const P&... p)
{
   return t + sum(p...);
}

int main()
{
   cout << sum(5, 10.0, 22.2) << endl;
}
Tomaka17
  • 4,832
  • 5
  • 29
  • 38
  • For non default constructible types, the above does not work, one needs to replace `typedef decltype(T() + typename TypeOfSum::type())` by `typedef decltype(std::declval() + std::declval::type>())` to avoid issues – Marti Nito Dec 10 '14 at 20:30
3

Another answer to the last question with less typing by using C++11's std::common_type: Simply use

std::common_type<T, P ...>::type

as return type of your variadic sum.

Regarding std::common_type, here is an excerpt from http://en.cppreference.com/w/cpp/types/common_type:

For arithmetic types, the common type may also be viewed as the type of the (possibly mixed-mode) arithmetic expression such as T0() + T1() + ... + Tn().

But obviously this works only for arithmetic expressions and doesn't cure the general problem.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • While this might work, it poses the following problem: Assume you want to calculate the sum over possibly different expression templates. With your approach, head + sum(tail...) will not benefit from the optimizations that the expression templates can provide! – Marti Nito Dec 10 '14 at 20:10
  • @MartiNito: I don't exactly understand what you're meaning. First, the return type is nothing which involves optimizations (as long it is correct, i.e. doesn't involve an expensive conversion or so). Second, `std::common_type` determines the type all `T...` can be implicitly converted to. So whether you can use it on expression templates depends on how you define implicit conversions in these. But I think for expression templates one usually would stick to an explicit determination of the return type via template recursion (as in the other answers). – davidhigh Dec 11 '14 at 01:15
  • Assume you work with vectors V matrices M and scalars s. Then within he sum a1+a2+a3 with a1 = s*V, a2 = M*V and a3 = M.col(1) each of those summands will a different type (reflecting the underlying operation). It is advantageous to delay the evaluations and perform them in the loop which is required for the sum. If common_type of a1 and a2 is a evaluated vector, then sum(a1+a2,a3) will loop twice, once for a1+a2 and once for (a1+a2)+a3. Of course, proper specialization of common_type to reflect the expression template of the operation a1+a2+a3 will do the trick. Hth – Marti Nito Dec 11 '14 at 15:51
2

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

smac89
  • 39,374
  • 15
  • 132
  • 179
  • Just a question out of curiosity, why does the order matter so much here? I tried writing a simple example and realized that if you swap the order of the two struct definitions it no longer compiles. (in other words if you put the `template ` struct definition before the `template ` struct definition.) – tjwrona1992 Dec 06 '18 at 18:32
  • @tjwrona1992 The second struct definition is a specialization of the first. See [`partial template specialization`](https://en.cppreference.com/w/cpp/language/partial_specialization). – smac89 Dec 06 '18 at 18:41
0

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).

anton_rh
  • 8,226
  • 7
  • 45
  • 73
-1

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.

Coral Kashri
  • 3,436
  • 2
  • 10
  • 22