5

Lets say I have a tuple std::tuple<Operation<1>, Operation<2>, Operation<3>>. Operation<> has a member function with the signature SomeType someFunction(SomeType). What I want to do is to call the operations successively such that the resulting order of calls would be Operation<3>::someFunction(Operation<2>::someFunction(Operation<1>::someFunction())) and I would get the final SomeType value. How do I achieve this using variadic templates (I have access to C++17)?

I can call each member function with std::apply([](auto& ...x) { (..., x.someFunction()); }, tuple); but what kind of expression do I need to call someFunction() with the output of the previous call?

Dago
  • 1,349
  • 1
  • 11
  • 19
  • I can iterate over a tuple with std::apply and fold expression but that question does not answer how to do it in this recursive manner so not a duplicate. – Dago Mar 20 '19 at 08:04
  • Possible duplicate of [How can you iterate over the elements of an std::tuple?](https://stackoverflow.com/questions/1198260/how-can-you-iterate-over-the-elements-of-an-stdtuple) – Michael Veksler Mar 20 '19 at 08:07
  • It's a fold over an `std::tuple`: https://www.boost.org/doc/libs/1_68_0/libs/hana/doc/html/group__group-Foldable.html#gaa0fde17f3b947a0678a1c0c01232f2cc – Rerito Mar 20 '19 at 08:09
  • Added some more clarification to the post. I understand how can I call a member function on each tuple member but how do I use the result in a new call I don't understand. – Dago Mar 20 '19 at 08:23
  • Sounds like you need a [y-combinator](https://yongweiwu.wordpress.com/2014/12/14/y-combinator-and-cplusplus/), but it's too early in the morning for me to get the code right enough for an answer... – Max Langhof Mar 20 '19 at 08:37

2 Answers2

4

I suppose you can combine std::apply() and template folding with a lambda as follows

   auto l = [&val](auto ... Ops) 
    { ((val = Ops.someFunc(val)), ...); };

The following is a full working example

#include <tuple>
#include <iostream>

template <int I>
struct Oper
 {
   static constexpr int someFunc (int i)
    { return i + I; }
 };

int main ()
 {
   std::tuple<Oper<1>, Oper<2>, Oper<3>, Oper<4>>  t;

   int val {}; // starting value

   auto l = [&val](auto ... Ops) 
    { ((val = Ops.someFunc(val)), ...); };

   std::apply(l, t);

   std::cout << val << std::endl;
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • This was exactly what I was looking for, thanks! I knew it is something simple but I just can't quite wrap my head around these folding expressions yet... – Dago Mar 20 '19 at 08:47
1

@max66's solution is elegant and concise, however one caveat is that all your operations must handle and return the same type (which is your case), I will try to propose a broader approach.

The idea is to rely on an overloaded operator>> to apply the desired operation on a state and the next step. To do so let's first define some building blocks:

// Just to avoid the hassle of std::forwarding by hand everywhere
#define CPPFWD(x) std::forward<decltype(x)>(x)

// We do not want to pollute the global namespace with our special operator>>
namespace combine {

// This will make the appropriate functor for each step
template <typename T, typename Op>
auto make_operation(T&& tuple_element, Op&& op) {
    return [ el = CPPFWD(tuple_element),
             op = CPPFWD(op) ](auto&& input) mutable {
        return op(el, CPPFWD(input));
    };
}

template <typename Input, typename Op>
auto operator>>(Input&& input, Op&& op) {
    return CPPFWD(op)(CPPFWD(input));
}


} // ns combine

Now we are ready to tackle the left fold implementation:

template <typename State, typename Tuple, typename Op, size_t... Is>
auto fold_left_impl(State&& state, Tuple&& tuple, Op&& op, std::index_sequence<Is...>) {
    using combine::make_operation;
    // We want our operator>> to be in the immediate scope here
    // to avoid selecting an inappropriate hypothetical overload 
    using combine::operator>>;
    using std::get;

    return (CPPFWD(state) >> ... >> make_operation(get<Is>(CPPFWD(tuple)), op));
}

Finally the function exposed to the end-user:

template <typename T>
using remove_cvref_t = std::remove_cv_t< std::remove_reference_t< T > >;

template <typename State, typename Tuple, typename Op>
auto fold_left(State&& state, Tuple&& tuple, Op&& op) {
    return fold_left_impl(
        CPPFWD(state),
        CPPFWD(tuple),
        CPPFWD(op),
        std::make_index_sequence< std::tuple_size< remove_cvref_t< Tuple > >::value > {} );
}

In your case, the correct usage would be:

std::tuple<Operation<1>, Operation<2>, Operation<3>> t;
fold_left(
    0,
    t,
    [](auto&& op, auto&& in) {
        return CPPFWD(op).someFunc(CPPFWD(in));
    } );

A live example can be found on Coliru

Rerito
  • 5,886
  • 21
  • 47