2017-09-05 77 views
3

比方说,我用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

我错过了什么吗?有些情况下会失败吗?

+0

那么,如果你正在移动它,'TupleArgs&args'应该是'TupleArgs args'或'TupleArgs && args'。其他任何情况都会导致传入的对象发生变化,从而使其非常不透明。 – Yakk

+0

@Yakk对,这是需要考虑的事情,它可能更适合作为转发参考。按值传递它只会创建一个底层引用的副本(这也是我猜的好) – Curious

+0

现在它应该是'std :: forward '而不是'move',因为'TupleArgs &&'不必是右后卫;)(如果它是一个右值ref,则foward无论如何都会做正确的事情,这个想法是使代码的正确性成为本地的一个属性) – Yakk

回答

3
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语义,它可以让你用更加标准的代码代替它。

+1

但是,当元组是左值时,并且一个元组元素是'T &&',而不是将元组转发到'get <>'将导致'T&'可能会创建一个副本等等。或者你的意思是说,这应该留下来,因为不移动参数这是有道理的idk – Curious

+3

如果元组是一个左值,那么这是正确的。一个左值元组声明调用者可以在稍后重用元组(如你可以在我的调用'foo'中看到的两次例如),在这种情况下,在第一次调用时,从参数移动到*错误*第二次调用,我承诺不关心元组的状态,因此'移动' – Yakk

+0

只是出于好奇,你为什么介绍lambda?你新例子中的'foo()'包装? – Curious