1

Possible Duplicate:
trailing return type using decltype with a variadic template function

I'm getting this compiler error:

g++ -std=gnu++0x -I. -O3 -Wall sum.cpp
sum.cpp:7:41: sorry, unimplemented: cannot expand ‘Remaining ...’ into a fixed-length argument list
sum.cpp: In function ‘int main(int, const char**)’:
sum.cpp:29:23: error: no matching function for call to ‘sum(int, int, int)’
sum.cpp:29:23: note: candidate is:
sum.cpp:20:6: note: template<class FirstArg, class ... RemainingArgs> decltype (Sum<FirstArg, RemainingArgs ...>::sum(first, sum::args ...)) sum(const FirstArg&, const RemainingArgs& ...)

for this sum.cpp:

#include <iostream>
#include <type_traits>

template <typename First, typename... Remaining>
struct Sum {
   static auto sum(const First &arg, const Remaining &... args)
      -> decltype(arg + Sum<Remaining...>::sum(args...))
   {
      return arg + sum(args...);
   }
};

template <typename First>
struct Sum<First>
{
   static First sum(const First &arg) { return arg; }
};

template <typename FirstArg, typename... RemainingArgs>
auto sum(const FirstArg &first, const RemainingArgs &... args)
   -> decltype(Sum<FirstArg, RemainingArgs...>::sum(first, args...))
{
   return Sum<FirstArg, RemainingArgs...>::sum(first, args...);
}

int main(int argc, const char *argv[])
{
   using ::std::cout;
   cout << sum(1, 2, 3) << '\n';

   return 0;
}

I've tried multiple means of declaring this function. Is this just a problem with gcc 4.6.1?


Edit: Here is what I finally went with because I don't really trust the ::std::common_type template. It still has a problem though, it will bind + from right to left instead of from left to right. This may cause problems with non-commutative + operators. That's not very hard to fix though:

#include <iostream>
#include <type_traits>
#include <utility>

namespace {
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... > {};
}

template <typename T>
T sum(const T &&arg)
{
   return ::std::forward<const T>(arg);
}

template <typename FirstArg, typename SecondArg, typename... RemainingArgs>
auto sum(const FirstArg &&first, const SecondArg &&second,
         const RemainingArgs &&... args)
   -> typename sum_type<FirstArg, SecondArg, RemainingArgs...>::type
{
   using ::std::forward;

   return forward<const FirstArg>(first) + \
      sum(forward<const SecondArg>(second),
          forward<const RemainingArgs>(args)...);
}

int main(int argc, const char *argv[])
{
   using ::std::cout;
   cout << sum(1, 2, 3.2) << '\n';

   return 0;
}
Community
  • 1
  • 1
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • 2
    The short answer: you can't use `decltype` of the very function template you're defining in a trailing-return-type, because it's not declared yet. – aschepler Nov 13 '11 at 23:01
  • Oops, I think I assumed this was a duplicate too soon. Actually, I see only one minor problem here, but the idea might work. – aschepler Nov 13 '11 at 23:10
  • FYI, the unimplemented functionality GCC is complaining about here has been implemented for 4.7 :) – R. Martinho Fernandes Nov 14 '11 at 00:24

1 Answers1

6

The first inner sum function that's being called recursively has the wrong signature.

For a run-time variadic function, try this:

#include <type_traits>
#include <utility>

template <typename T> T sum(T && x) { return std::forward<T>(x); }

template <typename T, typename ...Args>
typename std::common_type<T, Args...>::type sum(T && x, Args &&... args)
{
  return std::forward<T>(x) + sum(std::forward<Args>(args)...);
}


int main()
{
  return sum(1,2,3,4);
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • I want to sum a bunch of values that are only known at runtime even though their types are (of course) known at compile time. – Omnifarious Nov 13 '11 at 23:08
  • @Omnifarious: OK, let me rework this. The current code doesn't work anyway. I'll be back. – Kerrek SB Nov 13 '11 at 23:09
  • This works, though using `std::common_type` feels like cheating. :-) And why the rvalue references? – Omnifarious Nov 13 '11 at 23:21
  • 1
    @Omniarious: It's perfect forwarding. – Cat Plus Plus Nov 13 '11 at 23:25
  • 2
    @Omnifarious: They're not rvalue references, they're Berlusconi references: They'll be whatever you want them to be! (Observe that we're in a deducible context of a template, not in a function.) – Kerrek SB Nov 13 '11 at 23:29
  • @KerrekSB: Did you just make up this 'Berlusconi reference' thing to be funny, or is it a name other people use, and if the latter, where can I find a reference to the idea? – Omnifarious Nov 13 '11 at 23:40
  • @Omnifarious: It's a relatively new term ;-) You should look up "reference collapsing rules" to see how this type deduction works in the presence of `&&`. Herb Sutter calls it "an arcane rule", but it makes good sense. – Kerrek SB Nov 13 '11 at 23:43
  • @KerrekSB: So I can stuff `const`s in there to express that `sum` will not change its arguments. – Omnifarious Nov 13 '11 at 23:49
  • @Omnifarious: I wouldn't bother with that. If the object you pass in at the call site is const, then `T&&` will deduce as `U const &` automatically (with `T = const U`). – Kerrek SB Nov 13 '11 at 23:52
  • 1
    @KerrekSB: +1 Your solution is very elegant and so much cleaner and generally nice than what the OP has posted - good work! – Xander Tulip Nov 13 '11 at 23:56
  • 1
    The other day I implemented a `conjunction` function that AND-ed conditions passed as variadic arguments. Now this turns up. There is a pattern here known as fold, aggregate, or reduce, depending on where you come from. It can be generalized: http://www.ideone.com/XjWWt. – R. Martinho Fernandes Nov 14 '11 at 00:04
  • @R.MartinhoFernandes: It's also available as `accumulate` in `` as a function version that operates on iterators. You would pick the "from-the-left" vs "from-the-right" by chosing either begin/end or rbegin/rend ranges. – Kerrek SB Nov 14 '11 at 00:07
  • @KerrekSB: The point is not to handle const reference arguments properly, the point is to let the compiler (and everybody else) know that even if a non-const reference argument is passed in that it will not be modified by the function. – Omnifarious Nov 14 '11 at 00:32
  • There is no function, there is only a template! Important difference :-) If you have an immutable object, it *will* cause the appropriate function template instantiation. And if not, then there's nothing to worry about. (If you really, really must, you can call it with `sum(static_cast(x), 2, 3)`, but that will gain you *nothing*. – Kerrek SB Nov 14 '11 at 00:33