4

I've got a custom class that has a tuple-like interface. Because I want my code to be as generic as possible, I thought that it would be a good idea to base my algorithms on the functions std::get, std::tuple_size, std::tuple_element so you just have to specialize these functions to use my algorithms. Let's call the concept that requires these function specializations Tuple.

Now I am trying to sum up the components of a Tuple. The function declaration should be something like this:

template <class Tuple>
int sum_components(const Tuple& t);

I guess that there is a lot of template programming involved but I just can't figure out how to do it.

For the addition I would just use an overload of the global + operator.

I am using c++1z.

krzaq
  • 16,240
  • 4
  • 46
  • 61
Max
  • 63
  • 1
  • 5

2 Answers2

12

This is very easy in .

template<class Tuple>
decltype(auto) sum_components(Tuple const& tuple) {
  auto sum_them = [](auto const&... e)->decltype(auto) {
    return (e+...);
  };
  return std::apply( sum_them, tuple );
};

or (...+e) for the opposite fold direction.

In previous versions, the right approach would be to write your own apply rather than writing a bespoke implementation. When your compiler updates, you can then delete code.

In , I might do this:

// namespace for utility code:
namespace utility {
  template<std::size_t...Is>
  auto index_over( std::index_sequence<Is...> ) {
    return [](auto&&f)->decltype(auto){
      return decltype(f)(f)( std::integral_constant<std::size_t,Is>{}... );
    };
  }
  template<std::size_t N>
  auto index_upto() {
    return index_over( std::make_index_sequence<N>{} );
  }
}
// namespace for semantic-equivalent replacements of `std` code:
namespace notstd {
  template<class F, class Tuple>
  decltype(auto) apply( F&& f, Tuple&& tuple ) {
    using dTuple = std::decay_t<Tuple>;
    auto index = ::utility::index_upto< std::tuple_size<dTuple>{} >();
    return index( [&](auto...Is)->decltype(auto){
      auto target=std::ref(f);
      return target( std::get<Is>( std::forward<Tuple>(tuple) )... );
    } ); 
  }
}

which is pretty close to std::apply in . (I abuse std::ref to get INVOKE semantics). (It does not work perfectly with rvalue invokers, but that is very corner case).

In , I would advise upgrading your compiler at this point. In I'd advise upgrading your job at this point.


All of the above do right or left folds. In some cases, a binary tree fold might be better. This is trickier.

If your + does expression templates, the above code won't work well due to lifetime issues. You may have to add another template type for "afterwards, cast-to" to cause the temporary expression tree to evaluate in some cases.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Just needs some `decltype(auto)` sprinkling. – T.C. Nov 22 '16 at 19:54
  • @T.C. Added to taste. Omitted them originally because I didn't want to think about expression templates and other mess. There is a slim chance that intermediate results returned as `T&&` from a field of a temporary automatic storage object could break with the above. But now I think that is the expression template's problem. – Yakk - Adam Nevraumont Nov 22 '16 at 20:18
  • Thanks very much. This solution is even better than @krzaq's because you make use of `std::apply` which I didn't know of before. I accepted your solution instead. – Max Nov 22 '16 at 23:18
  • In C++03 I'd advise upgrading your job. Well said :o) – Arnaud Oct 19 '17 at 12:40
7

With C++1z it's pretty simple with fold expressions. First, forward the tuple to an _impl function and provide it with index sequence to access all tuple elements, then sum:

template<typename T, size_t... Is>
auto sum_components_impl(T const& t, std::index_sequence<Is...>)
{
    return (std::get<Is>(t) + ...);
}

template <class Tuple>
int sum_components(const Tuple& t)
{
    constexpr auto size = std::tuple_size<Tuple>{};
    return sum_components_impl(t, std::make_index_sequence<size>{});
}

demo


A C++14 approach would be to recursively sum a variadic pack:

int sum()
{
    return 0;
}

template<typename T, typename... Us>
auto sum(T&& t, Us&&... us)
{
    return std::forward<T>(t) + sum(std::forward<Us>(us)...);
}

template<typename T, size_t... Is>
auto sum_components_impl(T const& t, std::index_sequence<Is...>)
{
    return sum(std::get<Is>(t)...);
}

template <class Tuple>
int sum_components(const Tuple& t)
{
    constexpr auto size = std::tuple_size<Tuple>{};
    return sum_components_impl(t, std::make_index_sequence<size>{});
}

demo

A C++11 approach would be the C++14 approach with custom implementation of index_sequence. For example from here.


As @ildjarn pointed out in the comments, the above examples are both employing right folds, while many programmers expect left folds in their code. The C++1z version is trivially changeable:

template<typename T, size_t... Is>
auto sum_components_impl(T const& t, std::index_sequence<Is...>)
{
    return (... + std::get<Is>(t));
}

demo

And the C++14 isn't much worse, but there are more changes:

template<typename T, typename... Us>
auto sum(T&& t, Us&&... us)
{
    return sum(std::forward<Us>(us)...) + std::forward<T>(t);
}

template<typename T, size_t... Is>
auto sum_components_impl(T const& t, std::index_sequence<Is...>)
{
    constexpr auto last_index = sizeof...(Is) - 1;
    return sum(std::get<last_index - Is>(t)...);
}

demo

Community
  • 1
  • 1
krzaq
  • 16,240
  • 4
  • 46
  • 61
  • 1
    Interesting that you used a left fold for C++14 but a right fold for C++17. ;-] – ildjarn Nov 20 '16 at 03:12
  • @ildjarn Good point. I didn't do this consciously, but I think it was because I instinctively wanted to keep the "known" element on the left hand side. But if his op+ isn't associative or commutative then OP has an another bag of problems ;) – krzaq Nov 20 '16 at 03:18
  • Agreed, _shouldn't_ matter for `sum` (though `+` isn't necessarily safe – `std::string`), but in general I think people who are being exposed to fold expressions for the first time should be shown left folds, as that's what most C++ programmers are going to expect (anyone with an FP background won't be fooled either way once they know both are possible). Not a criticism of your answer, just an observation. :-D – ildjarn Nov 20 '16 at 03:22
  • @ildjarn Criticism or not, updated. Examples should be consistent now. And I appreciate the feedback, hopefully I'll remember that the mental model of "LHS @ REST" isn't the best way to go :) – krzaq Nov 20 '16 at 03:27
  • @ildjarn wait, is it really left fold in the C++14 solution? It's e1 + ( e2 + ( e3 + (en-1 + en))), which is described as right fold on the cppreference page for fold expressions. – krzaq Nov 20 '16 at 03:33
  • Gosh! Nice answere :) Just for the record, would you consider it bad style to use the global `+ operator` as I suggested? Would it be better to use a `Callable` that one can pass as an template argument? If yes, I couldn't use fold expressions and I would have to use the C++14 version, right? – Max Nov 20 '16 at 03:36
  • @Max yeah, fold expressions require operators. I'd do what stdlib containers do - provide two versions, one using operators and one with custom binary operation. Or maybe just the second one that would have defaulted to `std::plus<>` as the binary operator. – krzaq Nov 20 '16 at 03:38
  • @krzaq Which stdlib functions/ methods make use of fold expressions? – Max Nov 20 '16 at 03:41
  • @Max not fold expressions. But look for algorithms, i.e. `std::sort`. There are basically two versions: one that takes 2 iterators and uses `operator<` for comparison and another that additionally takes a callable comparator. You could do the same thing for your algorithm. – krzaq Nov 20 '16 at 03:44
  • @krzaq : Hah, good point, shows how much I'm _really_ paying attention... ;-] Well I'll maintain that left folds make more sense to most C++ programmers (due to `std::accumulate` et al), but for consistency between both examples you're right that it should be a right fold here after all! – ildjarn Nov 20 '16 at 03:45
  • @ildjarn I'll happily change the implementation of both to left fold (I claim no expertise in this matter), but I can't think of a simple implementation of it in C++14. – krzaq Nov 20 '16 at 03:49
  • @krzaq Ok, thanks very much! I'll go with the C++14 version and use a defaulted plus operator :) – Max Nov 20 '16 at 03:49
  • @krzaq : Likewise. ;-] – ildjarn Nov 20 '16 at 03:51
  • @ildjarn I thought of something and it seems to work, but I'd appreciate a second set of eyes. – krzaq Nov 20 '16 at 03:55
  • @krzaq : That processes values in reverse, so definitely loses commutativity ([demo](http://coliru.stacked-crooked.com/a/6d58fe9c4fff1a2f)); again, shouldn't be a problem for `sum`, but in general... [This](http://coliru.stacked-crooked.com/a/99cf2a3b5deb7931) does the trick as long as `+=` isn't pathological, though it's not exactly pretty... – ildjarn Nov 20 '16 at 04:18
  • @ildjarn what if you reversed the order of arguments for op+? ([link](http://coliru.stacked-crooked.com/a/7be06ccb37aa9a0c)) If it's wrong then I don't think I have an idea prettier than yours with `+=` – krzaq Nov 20 '16 at 04:25
  • 1
    @krzaq : Wrong link? Assuming you meant [this](http://coliru.stacked-crooked.com/a/7be06ccb37aa9a0c), it seems to do the trick. :-D The termination case is explicitly typed though, which I don't like; fixing would involve `std::common_type` somewhere plus extra template parameters across the board to pass it around – yuck. <3 fold expressions... – ildjarn Nov 20 '16 at 04:27