C++ 使用std::forward\u作为\u元组模拟std::forward
假设我使用C++ 使用std::forward\u作为\u元组模拟std::forward,c++,c++11,tuples,forwarding,C++,C++11,Tuples,Forwarding,假设我使用std::forward\u as\u tuple将函数调用的参数存储在一个tuple中 auto args = std::forward_as_tuple(std::forward<Args>(args)...); 这是可行的,因为std::get的右值限定版本返回std::tuple_元素&&,这是std::tuple_元素&的引用的身份转换,因为引用与&一起折叠 因此,如果std::tuple\u element\u t的计算结果为t&,则返回的类型将是t&&&,即
std::forward\u as\u tuple
将函数调用的参数存储在一个tuple中
auto args = std::forward_as_tuple(std::forward<Args>(args)...);
这是可行的,因为std::get
的右值限定版本返回std::tuple_元素&&
,这是std::tuple_元素&
的引用的身份转换,因为引用与&
一起折叠
因此,如果std::tuple\u element\u t
的计算结果为t&
,则返回的类型将是t&&&
,即t&
。当std::tuple\u element\u t
返回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))...);
}
我们根本不需要触摸向前移动到\u foo
,而且我们从参数中移动的次数不会超过一次
在您最初的实现中,对forward\u to_foo
的任何调用都会从TupleArgs
rvalue引用或值静默地移动,而不会在调用站点显示我们对第一个参数的破坏性
除了那个细节,是的,它模拟了转发
我自己会写一个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 >{}
);
}
}
namespace notstd{
命名空间详细信息{
模板
decltype(自动)应用(F&&F、TupleArgs&&args、,
std::索引(U序列){
返回std::forward(f)(std::get(std::forward(args))…);
}
}
模板
decltype(自动)应用(F&&F、TupleArgs&&args){
constexpr auto count=std::tuple\u size{};
返回详细信息::应用(
标准:正向(f),
标准::转发(args),
std::make_index_sequence{}
);
}
}
然后我们做:
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) );
auto tuple_args=std::forward_as_tuple(std::forward(args)…);
自动调用_foo=[](自动&&…args)->decltype(自动){返回foo(decltype(args)(args);};
returnnotstd::apply(call_foo,std::move(tuple_args));
它将棘手的部分移到了notstd::apply
,它试图匹配std::apply
的语义,这让您可以用更标准的代码位替换它。好吧,TupleArgs&args
应该是TupleArgs&args
或者TupleArgs&args
,如果您要离开它。在调用的过程中,任何其他东西都会变得不透明,即被传递的对象发生了突变。通过值传递它只会创建底层引用的副本(我想这也很好),现在它应该是std::forward
而不是move
,因为TupleArgs&&
不必是右值引用;)(如果它是一个右值引用,那么foward无论如何都会做正确的事情。其想法是将代码正确性作为一个本地属性作为一个合理的属性)@Yakk我认为std::forward
可能不会做正确的事情,例如std::tuple\u element\u t
是一个t&
,而forward会导致一个左值引用,然后std::tuple\u元素\u t&&&
将导致t&
,这不是我想要的。但是如果传入的tuple被重用,这是正确的。只有在您还承诺不在其他上下文中重用元组本身的情况下,从元组的右值引用成员移动才有效!但是,如果元组是左值,并且一个元组元素是T&
,不将元组转发到get
将导致T&
,这可能会创建一个副本,等等。或者,您的意思是,如果不首先将参数移动到函数,就应该保留该元素吗?如果元组是左值,那么这是有意义的。左值元组表示调用者以后可以重用该元组(正如您在我的callfoo
示例中看到的)。在这种情况下,在第一次调用中从参数移动是错误的。第二个调用我承诺不关心元组的状态,因此从元组中移动。只是出于好奇,为什么在新示例中为foo()
引入lambda包装器?@quiriedfoo
可能有重载。如果不选择要使用的重载,则无法将重载函数名作为对象传递。
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 );
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) );