0

I have the following function. It converts two bindings of T0 and T1 to a binding of a tuple<T0,T1>

The function is as follows

template<typename T0, typename T1>
typename RxBinding<std::tuple<T0,T1>>::Ptr
Combine(RxBinding<T0>::Ptr b0, RxBinding<T1>::Ptr b1)
{
    using Tuple = std::tuple<T0,T1>;
    RxBinding<Tuple>::Ptr binding = makeValueBinding(std::make_tuple(b0->Get(),b1->Get()));

    // Break the reference cycle. 
    auto bindingWeak = std::weak_ptr<RxBinding<Tuple>>(binding);

    auto s0 = b0->Subscribe([bindingWeak,b1](T0 const & v0){
        auto b = bindingWeak.lock();
        if(b)
            b->Update(std::make_tuple(v0,b1->Get()));
    });

    auto s1 = b1->Subscribe([bindingWeak,b0](T1 const & v1){
        auto b = bindingWeak.lock();
        if(b)
            b->Update(std::make_tuple(b0->Get(),v1));
    });

    auto sN =  binding->Subscribe([b0,b1](std::tuple<T0,T1> const & t){
        b0->Update(std::get<0>(t));
        b1->Update(std::get<1>(t));
    });

    binding->CleanupWith << s0 << s1 << sN;

    return binding;
}

Don't worry too much about what a binding is. Assume they work. I'm looking for a pattern to generalise this using C++11 variadic templates so I can have N bindings as input rather than just two and convert them to a single binding?

template <typename ...T>
typename RxBinding<std::tuple<T...>>::Ptr
Combine( RxBinding<T>::Ptr args...) /* is this possible ?? */
{
    using Tuple = std::tuple<T...>;
    auto binding = makeValueBinding(std::make_tuple( /* what do do here with args ?? */ ));

    // Break the reference cycle. 
    RxBinding<Tuple>::Ptr bindingWeak = std::weak_ptr<RxBinding<Tuple>>(binding);

    // Make N subscriptions b0,b1,....bN with the weak reference above

    /* What to do here ?? */

    // Make the final subscription

    auto sN = binding->Subscribe([](std::tuple<T...> const & t){
        // Update the N bindings.

        /* what to do here ? */

    });

    // Add all subscriptions to the cleanup on the final binding
    /* not sure what to do here */

    return binding;
}
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • 1
    "Don't worry too much about what a binding is. Assume they work" well, how can we write a code around a "thing" that we don't know what it is, what is supposed to do and what its API is? For instance in your first example `s0` references `b1` and `s1` references `b0`. How are we supposed to know how to generalize that for an arbitrary number of `b`s? – bolov May 06 '20 at 09:00
  • 1
    It would be good if you could provide us a [MRE] that we can actually compile/run/debug. – NutCracker May 06 '20 at 09:26
  • What is the logic for `b1->Subscribe([bindingWeak,b0](T1 const & v1){..}`? each pair, prev/next? – Jarod42 May 06 '20 at 10:46
  • Is `Combine(b0, b1, b2)` equivalent to `Combine(Combine(b0, b1), b2)` ? – Jarod42 May 06 '20 at 10:47
  • @Jarod42 ``Combine(b0,b1,b2)`` would not be the same as ``Combine(Combine(b0,b1),b2)`` as the first would generate bindings on ``tuple`` and the second would generate bindings on ``tuple,T2>`` – bradgonesurfing May 06 '20 at 11:20
  • Think of RxBinding as a duplex channel and Combine is a multiplexor which takes N duplex channels and generates a new single duplex channel. Maybe later I'll try again with something a bit more simple to figure out the pattern. Seems a bit too vauge for SO unfortunately. – bradgonesurfing May 06 '20 at 11:22
  • This might be close to what I'm looking for https://stackoverflow.com/questions/45841555/map-variadic-template-arguments – bradgonesurfing May 06 '20 at 11:25
  • Maybe also provide version with 3 argument to see the logic – Jarod42 May 06 '20 at 11:39

1 Answers1

1

From RxBinding<T>::Ptr T can't be deduced, as it's a non-deduced context because of nested types (see example 1) under Non-deduced contexts on cppreference and godbolt example), so the original example shouldn't have worked with argument deduction. With that in mind having typename RxBinding<Ts>::Ptr ...args will work the same way as it did before (note the syntax having ... before the argument name). I changed the variadic type template to Ts instead of T, to better represent that it's variadic.

With auto binding = makeValueBinding(std::make_tuple( /* what do do here with args ?? */ )); you can use a pack expansion with the pattern args->Get(), so the final line will be
auto binding = makeValueBinding(std::make_tuple(args->Get()...));.

Creation of the variables s0, s1, and so on is not trivial, so I'll get back to it at the end.

To make the final subscribtion, you will need to use a helper function to expand the tuple:

template<typename ...ArgTypes, typename ...Ts, std::size_t ...Ns>
void FinalSubscribeHelper(
    std::tuple<ArgTypes...> const &args,
    std::tuple<Ts...> const &t,
    std::index_sequence<Ns...>
)
{
    // using C++17's fold expressions (https://en.cppreference.com/w/cpp/language/fold)
    ((std::get<Ns>(args)->Update(std::get<Ns>(t))), ...); // we use the comma operator for expansion
    return;

    // using array initializers for C++11
    using ArrayT = int[sizeof...(ArgTypes)];
    ArrayT{
        ((
            std::get<Ns>(args)->Update(std::get<Ns>(t)) // this is the pattern
        ), 0)...
    };
    return;
}

So the final subscribtion is

auto sN = binding->Subscribe([=](std::tuple<Ts...> const &t){
    // Update the N bindings.
    FinalSubscribeHelper(std::make_tuple(args...), t, std::make_index_sequence<sizeof...(Ts)>{});
});

For adding all subscriptions to the cleanup you will need another helper function:

template<typename BindingT, typename ...STs, typename SNT, std::size_t ...Ns>
void CleanupHelper(
    BindingT const &binding,
    std::tuple<Ts...> const &s,
    SNT const &sN
    std::index_sequence<Ns...>
)
{
    // using C++17's fold expressions (https://en.cppreference.com/w/cpp/language/fold)
    (binding->CleanupWith << ... << std::get<Ns>(s)) << sN;
    return;

    // using array initializers for C++11
    /*
    this only works if
    binding->CleanupWith << s0 << s1 << sN;
    is equivalent to
    binding->CleanupWith << s0;
    binding->CleanupWith << s1;
    binding->CleanupWith << sN;
    */
    using ArrayT = int[sizeof...(ArgTypes)];
    ArrayT{
        ((
            binding->CleanupWith << std::get<Ns>(s)
        ), 0)...
    };
    binding->CleanupWith << sN;
    return;
}

So the final cleanup is CleanupHelper(binding, s, sN, std::make_index_sequence<sizeof...(Ts)>{});.

Now get back to creating s. To create the callback I assume you want Update to be called as
b->Update(std::make_tuple(/* bM->Get() with M = 0, 1, 2, ..., I-1 */, vI, /* bM->Get() with M = I+1, I+2, ..., N-1 */));. For this you need two index sequences, one from 0 to I-1 and one from I+1 to N-1. For that let's create some type aliases to make the needed std::index_sequence's.

template<std::size_t Offset, typename T>
struct AddOffset;

template<std::size_t Offset, std::size_t ...Ns>
struct AddOffset<Offset, std::index_sequence<Ns...>>
{
    using type = std::index_sequence<(Ns + Offset)...>;
};

template<std::size_t Offset, typename T>
using AddOffsetT = typename AddOffset<Offset, T>::type;

// this creates a std::index_sequence with the values
// Start, Start+1, Start+2, ..., End-1
template<std::size_t Start, std::size_t End>
using MakeIndexSequenceInRange = AddOffsetT<Start, std::make_index_sequence<End - Start>>;

To create s you will need a few helper functions:

template<typename BindingT, typename ...ArgTypes, typename VT, std::size_t ...Ns, std::size_t ...Ms>
void SubscribeCallbackHelper(
    BindingT const &b,
    std::tuple<ArgTypes...> const &args,
    VT const &v,
    std::index_sequence<Ns...>,
    std::index_sequence<Ms...>
)
{
    b->Update(std::make_tuple(std::get<Ns>(args)->Get()..., v, std::get<Ms>(args)->Get()...));
}

template<typename BindingWeakT, typename ...ArgTypes, std::size_t ...Ns>
auto CreateS(
    BindingWeakT const &bindingWeak,
    std::tuple<ArgTypes...> const &args,
    std::index_sequence<Ns...>
) -> decltype(std::make_tuple(std::get<Ns>(args)->Subscribe(std::declval<void(*)(ArgTypes const &)>())...))
// I'm not sure this decltype will work, if you have C++14 you should be able to just use auto as a return type
{
    return std::make_tuple(
        std::get<Ns>(args)->Subscribe([bindingWeak, args](ArgTypes const &v) {
            auto b = bindingWeak.lock();
            if (b)
                SubscribeCallbackHelper(b, args, v, MakeIndexSequenceInRange<0, Ns>{}, MakeIndexSequenceInRange<Ns+1, sizeof...(ArgTypes)>{});
        })
    );
}

So the creation of s will be

auto s = CreateS(bindingWeak, std::make_tuple(args...), std::make_index_sequence<sizeof...(Ts)>{});
IlCapitano
  • 1,994
  • 1
  • 7
  • 15
  • Notice that `std::index_sequence` is C++14, but implementation in C++11 is available on SO and elsewhere. – Jarod42 May 06 '20 at 14:23