用 std::forward_as_tuple 模拟 std::forward
Simulating std::forward with 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>
return std::tuple_element_t<Index, tuple<Types...>>&&
的右值限定版本是 std::tuple_element_t<Index, tuple<Types...>>
的引用性的身份转换,因为引用与 &&
一起崩溃。
因此,如果 std::tuple_element_t<Index, tuple<Types...>>
的计算结果为 T&
,则 returned 类型将为 T& &&
,即 T&
。 when std::tuple_element_t<Index, tuple<Types...>>
returns 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
的语义,这让您可以用更标准的代码替换它。
假设我使用 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>
return std::tuple_element_t<Index, tuple<Types...>>&&
的右值限定版本是 std::tuple_element_t<Index, tuple<Types...>>
的引用性的身份转换,因为引用与 &&
一起崩溃。
因此,如果 std::tuple_element_t<Index, tuple<Types...>>
的计算结果为 T&
,则 returned 类型将为 T& &&
,即 T&
。 when std::tuple_element_t<Index, tuple<Types...>>
returns 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
的语义,这让您可以用更标准的代码替换它。