2017-09-05 126 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

相關問題