如何使用可变参数模板 c ++ 泛化此函数

Posted

技术标签:

【中文标题】如何使用可变参数模板 c ++ 泛化此函数【英文标题】:How to generalize this function with variadic templates c++ 【发布时间】:2020-05-06 08:45:59 【问题描述】:

我有以下功能。它将T0T1 的两个绑定转换为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;

【问题讨论】:

“不要太担心绑定是什么。假设它们工作”很好,我们如何围绕一个我们不知道它是什么的“东西”编写代码,什么是应该做什么,它的 API 是什么?例如,在您的第一个示例中 s0 引用 b1s1 引用 b0。我们应该如何知道如何将其推广到任意数量的bs? 如果你能提供给我们一个minimal reproducible example,我们可以实际编译/运行/调试。 b1-&gt;Subscribe([bindingWeak,b0](T1 const &amp; v1)..的逻辑是什么?每对,上一个/下一个? Combine(b0, b1, b2) 是否等同于 Combine(Combine(b0, b1), b2) @Jarod42 Combine(b0,b1,b2)Combine(Combine(b0,b1),b2) 不同,因为第一个会在tuple&lt;T0,T1,T2&gt; 上生成绑定,第二个会在tuple&lt;tuple&lt;T0,T1&gt;,T2&gt; 上生成绑定 【参考方案1】:

来自RxBinding&lt;T&gt;::Ptr T 不能被推导,因为它是一个非推导上下文,因为在cppreference 和godbolt example 的非推导上下文下的嵌套类型(参见示例1),所以原来的示例不应该与参数推导一起使用。考虑到这一点,typename RxBinding&lt;Ts&gt;::Ptr ...args 的工作方式与以前相同(注意在参数名称前有 ... 的语法)。我将可变参数类型模板更改为Ts 而不是T,以更好地表示它是可变参数。

对于auto binding = makeValueBinding(std::make_tuple( /* what do do here with args ?? */ ));,您可以使用带有args-&gt;Get() 模式的pack expansion,因此最后一行将是auto binding = makeValueBinding(std::make_tuple(args-&gt;Get()...));

s0s1 等变量的创建并非易事,所以我会在最后再谈。

要进行最终订阅,您需要使用辅助函数来扩展元组:

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&lt;sizeof...(Ts)&gt;);.

现在返回创建s。要创建回调,我假设您希望将Update 称为b-&gt;Update(std::make_tuple(/* bM-&gt;Get() with M = 0, 1, 2, ..., I-1 */, vI, /* bM-&gt;Get() with M = I+1, I+2, ..., N-1 */));。为此,您需要两个索引序列,一个从0I-1,一个从I+1N-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)>);

【讨论】:

请注意,std::index_sequence 是 C++14,但 C++11 中的实现可在 SO 和其他地方使用。

以上是关于如何使用可变参数模板 c ++ 泛化此函数的主要内容,如果未能解决你的问题,请参考以下文章

C++11 ——— 可变参数模板

如何使用可变参数模板参数保存可变数量的参数?

可变参数模板

C++11:可变参数模板函数参数的数量?

C语言奇淫技巧之函数的可变参数

C/C++中的可变参数和可变参数模板