Как обобщить эту функцию с помощью вариативных c шаблонов c ++ - PullRequest
0 голосов
/ 06 мая 2020

У меня есть следующая функция. Он преобразует две привязки T0 и T1 в привязку tuple<T0,T1>

. Функция выглядит следующим образом:

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;
}

Не беспокойтесь о привязке является. Предположим, они работают. Я ищу шаблон, чтобы обобщить это, используя шаблоны вариативных c С ++ 11, чтобы я мог иметь N привязок в качестве входных данных, а не только две, и преобразовывать их в одну привязку?

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;
}

1 Ответ

1 голос
/ 06 мая 2020

From RxBinding<T>::Ptr T не может быть выведено, так как это невыведенный контекст из-за вложенных типов (см. Пример 1) в невыведенных контекстах на cppreference и godbolt example ), поэтому исходный пример не должен работать с выводом аргументов. Имея это в виду, наличие typename RxBinding<Ts>::Ptr ...args будет работать так же, как и раньше (обратите внимание на синтаксис, имеющий ... перед именем аргумента). Я изменил шаблон типа Variadi c на Ts вместо T, чтобы лучше представить, что это вариант c.

С auto binding = makeValueBinding(std::make_tuple( /* what do do here with args ?? */ )); вы можете использовать расширение pack с шаблоном args->Get(), поэтому последняя строка будет
auto binding = makeValueBinding(std::make_tuple(args->Get()...));.

Создание переменных s0, s1 и т. д. не является тривиальным делом, поэтому я вернитесь к нему в конце.

Чтобы сделать окончательную подписку, вам нужно будет использовать вспомогательную функцию для расширения кортежа:

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;
}

Таким образом, окончательная подписка будет

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)>{});
});

Для добавления всех подписок в очистку вам понадобится еще одна вспомогательная функция:

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;
}

Итак, окончательная очистка будет CleanupHelper(binding, s, sN, std::make_index_sequence<sizeof...(Ts)>{});.

Теперь вернемся к созданию s. Чтобы создать обратный вызов, я предполагаю, что вы хотите, чтобы Update назывался
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 */));. Для этого вам понадобятся две последовательности индексов: от 0 до I-1 и от I+1 до N-1. Для этого давайте создадим несколько псевдонимов типов, чтобы сделать необходимые std::index_sequence.

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>>;

Для создания s вам понадобятся несколько вспомогательных функций:

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)>{});
        })
    );
}

Итак, создание s будет

auto s = CreateS(bindingWeak, std::make_tuple(args...), std::make_index_sequence<sizeof...(Ts)>{});
...