模拟与性病的std ::向前:: forward_as_tuple
比方说,我用std::forward_as_tuple
给函数调用的参数存储在一个元组模拟与性病的std ::向前:: forward_as_tuple
auto args = std::forward_as_tuple(std::forward<Args>(args)...);
然后,我通过左值引用传递这个元组想要的功能用另一个std::integer_sequence
确定的args
中的一些参数调用函数foo()
。我std::move()
做到像这样
template <typename TupleArgs, std::size_t... Indices>
decltype(auto) forward_to_foo(TupleArgs&& args,
std::index_sequence<Indices...>) {
return foo(std::get<Indices>(std::move(args))...);
}
而且这样的工作,因为std::get<std::tuple>
回报std::tuple_element_t<Index, tuple<Types...>>&&
这与&&
折叠std::tuple_element_t<Index, tuple<Types...>>
因为参考的基准岬的身份转变右值合格的版本。
因此,如果std::tuple_element_t<Index, tuple<Types...>>
的计算结果为T&
返回的类型将是T& &&
这只是T&
。原因类似std::tuple_element_t<Index, tuple<Types...>>
返回T&&
和T
我错过了什么吗?有些情况下会失败吗?
template <typename TupleArgs, std::size_t... Indices>
decltype(auto) forward_to_foo(TupleArgs&& args,
std::index_sequence<Indices...>) {
return foo(std::get<Indices>(std::forward<TupleArgs>(args))...);
}
这是正确的实现。
使用应该像:
auto tuple_args = std::forward_as_tuple(std::forward<Args>(args)...);
forward_to_foo(std::move(tuple_args), std::make_index_sequence<sizeof...(args)>{});
这里有一些区别。
首先,我们通过转发参考,而不是通过左值参考。这让调用者为我们提供了右值(prvalue或xvalue)元组。
二,我们转发元组进入std::get
调用。这意味着如果元组被移动到我们的中,我们只能通过get
右值参考。我们搬到forward_to_foo
。这确保了上述做的是正确的事情。
现在想象一下,如果我们想拨打foo
两次。
auto tuple_args = std::forward_as_tuple(std::forward<Args>(args)...);
auto indexes = std::make_index_sequence<sizeof...(args)>{};
forward_to_foo(tuple_args, indexes);
forward_to_foo(std::move(tuple_args), indexes);
我们没有接触forward_to_foo
可言的,我们从来没有从任何args
不止一次的移动。
与您最初的实现,以forward_to_foo
任何调用默默地从TupleArgs
右值引用或值移动,而不会在调用点,我们是第一个参数破坏的迹象。
除了这个细节,是模拟转发。
我自己我只是写了notstd::apply
:
namespace notstd {
namespace details {
template <class F, class TupleArgs, std::size_t... Indices>
decltype(auto) apply(F&& f, TupleArgs&& args,
std::index_sequence<Indices...>) {
return std::forward<F>(f)(std::get<Indices>(std::forward<TupleArgs>(args))...);
}
}
template <class F, class TupleArgs>
decltype(auto) apply(F&& f, TupleArgs&& args) {
constexpr auto count = std::tuple_size< std::decay_t<TupleArgs> >{};
return details::apply(
std::forward<F>(f),
std::forward<TupleArgs>(args),
std::make_index_sequence<count>{}
);
}
}
然后我们做:
auto tuple_args = std::forward_as_tuple(std::forward<Args>(args)...);
auto call_foo = [](auto&&...args)->decltype(auto){ return foo(decltype(args)(args)...); };
return notstd::apply(call_foo, std::move(tuple_args));
其移动棘手位为notstd::apply
,它试图匹配std::apply
语义,它可以让你用更加标准的代码代替它。
那么,如果你正在移动它,'TupleArgs&args'应该是'TupleArgs args'或'TupleArgs && args'。其他任何情况都会导致传入的对象发生变化,从而使其非常不透明。 – Yakk
@Yakk对,这是需要考虑的事情,它可能更适合作为转发参考。按值传递它只会创建一个底层引用的副本(这也是我猜的好) – Curious
现在它应该是'std :: forward'而不是'move',因为'TupleArgs &&'不必是右后卫;)(如果它是一个右值ref,则foward无论如何都会做正确的事情,这个想法是使代码的正确性成为本地的一个属性) –
Yakk