c++ - 如何使用可变参数模板 c++ 泛化此函数
问题描述
我有以下功能。它将T0
和的两个绑定转换T1
为 a 的绑定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;
}
解决方案
FromRxBinding<T>::Ptr
T
不能被推导,因为它是一个非推导上下文,因为在cppreference和Godbolt 示例的非推导上下文下的嵌套类型(参见示例 1) ,所以原始示例不应该与参数推导一起使用。考虑到这一点,它typename RxBinding<Ts>::Ptr ...args
的工作方式与以前相同(注意...
参数名称之前的语法)。我将可变参数类型模板更改为,Ts
而不是T
,以更好地表示它是可变参数。
auto binding = makeValueBinding(std::make_tuple( /* what do do here with args ?? */ ));
您可以使用带有模式的包扩展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 */));
. 为此,您需要两个索引序列,一个 from 0
toI-1
和一个 from I+1
to N-1
。为此,让我们创建一些类型别名来制作所需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>>;
要创建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)>{});
推荐阅读
- java - Dockerized Spring Boot 不能在 Raspberry Hyperiot OS + Gitlab CI + Spring Boot 上运行
- javascript - 改变焦点上反应本机文本输入的状态
- python - 访问 Heron 中的本地文件夹
- amazon-s3 - AWS 限制对 s3 中子文件夹的访问
- java - 绑定结果不起作用弹簧启动
- html - 不需要的列布局 HTML/CSS
- javascript - VueJS for 循环创建组件,组件好像是连在一起的
- ios - iOS firebase 数据库设计问题:为云 Firestore 设计聊天应用程序的最佳方式?
- java - JavaFX:在 splashScreen 中使用不确定的进度条
- kubernetes - 如何使用 Kubectl 执行变量替换?