2015-04-27 37 views
2

忽略缺少的完美轉發。 (假設參數在實際執行完美轉發。)獲取第n個可變參數值(不是類型)

// Base case: no args 
template<typename TF> void forEach2Args(TF) { } 

// Recursive case: some args 
template<typename TF, typename... Ts> void forEach2Args(TF mFn, Ts... mXs) 
{ 
    mFn(getNth<0>(mXs...), getNth<1>(mXs...)); 
    forEach2Args(mFn, getAllAfter<2>(mXs...)); 
} 

int main() 
{ 
    int result{0}; 
    forEach2Args([&result](auto a1, auto a2) 
    { 
     result += (a1 * a2); 
    }, 2, 4, 3, 6); 

    // roughly evaluates to: 
    //  result += (2 * 4); 
    //  result += (3 * 6); 
} 

是否有可能實現getNthgetAllAfter避免任何可能的運行時開銷?到目前爲止,我發現的唯一解決方案是將第一個forEach2Args調用中的每個Ts...放在std::tuple的內部,然後將每個遞歸調用的非常量引用傳遞給該元組。我幾乎可以確定有不必要的移動/ ctor/dtor調用。

另一種解決方案是使用這樣的:

// Base case: no args 
template<typename TF> void forEach2Args(TF) { } 

// Recursive case: some args 
template<typename TF, typename T1, typename T2, typename... Ts> 
void forEach2Args(TF mFn, T1 mX1, T2 mX2, Ts... mXs) 
{ 
    mFn(mX1, mX2); 
    forEach2Args(mFn, mXs...); 
} 

但是這種解決方案需要,如果我想通過在3個而不是2,或任何其他數量的組參數,再次實現。我想要一些動態的地方,我可以指定通過模板參數傳遞給每個mFn調用的參數數量。喜歡的東西:

forEachNArgs<3>([](auto a1, auto a2, auto a3){ /*...*/ }, /*...*/); 
forEachNArgs<4>([](auto a1, auto a2, auto a3, auto a4){ /*...*/ }, /*...*/); 
+2

使用'的std :: tie'有不復制其參數的元組。 –

+0

@DanielFrey:看起來'std :: tie'帶有l值引用。如果我在'mFn'調用期間傳遞給'forEach2Args'的r值引用,它會阻止正確的轉發嗎? –

+1

@VittorioRomeo,看來正確的方法是'std :: forward_as_tuple(std :: forward (values)...)' – chris

回答

7

忽略的要求完美的轉發,這應該工作:

template<typename B, typename C> 
struct forEachNArgsImpl; 

template<std::size_t... Bs, std::size_t... Cs> 
struct forEachNArgsImpl< 
    std::index_sequence<Bs...>, 
    std::index_sequence<Cs...> 
> 
{ 
    template<std::size_t N, typename TF, typename... Ts> 
    static void execN(TF mFn, const std::tuple<Ts...>& mXs) 
    { 
     mFn(std::get< N + Cs >(mXs)...); 
    } 

    template<typename TF, typename... Ts> 
    static void exec(TF mFn, const std::tuple<Ts...>& mXs) 
    { 
     using swallow = bool[]; 
     (void)swallow{ (execN< Bs * sizeof...(Cs) >(mFn, mXs), true)... }; 
    } 
}; 

template<std::size_t N, typename TF, typename... Ts> 
void forEachNArgs(TF mFn, Ts... mXs) 
{ 
    static_assert(sizeof...(Ts) % N == 0, "Wrong number of arguments"); 
    forEachNArgsImpl< 
     std::make_index_sequence<sizeof...(Ts)/N>, 
     std::make_index_sequence<N> 
    >::exec(mFn, std::forward_as_tuple(mXs...)); 
} 

Live example

+0

啊,我看到你的編輯太晚了...... –

+0

在幾分鐘前添加了關於這個可能的實現的編輯 –

+0

@VittorioRomeo我已經想出了一個更通用和**非遞歸**版本,請查看編輯答案。 –

2

以下可能會有所幫助:

namespace detail 
{ 

    template<std::size_t...IsN, std::size_t...Is, typename F> 
    void forEachNArgsImpl(std::index_sequence<IsN...>, std::index_sequence<Is...>, F) { } 

    template<std::size_t...IsN, std::size_t...Is, typename F, typename... Ts> 
    void forEachNArgsImpl(std::index_sequence<IsN...> isn, std::index_sequence<Is...>, F f, Ts... mXs) 
    { 
     f(std::get<IsN>(std::forward_as_tuple(std::forward<Ts>(mXs)...))...); 
     constexpr std::size_t N = sizeof...(IsN); 
     constexpr std::size_t is = sizeof...(Is); 
     forEachNArgsImpl(isn, 
         std::make_index_sequence<(is > N) ? sizeof...(Is) - N : 0>{}, 
         f, 
         std::get<N + Is>(std::forward_as_tuple(std::forward<Ts>(mXs)...))...); 
    } 

} 

template<std::size_t N, typename F, typename... Ts> void forEachNArgs(F f, Ts... args) 
{ 
    static_assert(sizeof...(Ts) % N == 0, "Wrong number of arguments"); 
    detail::forEachNArgsImpl(std::make_index_sequence<N>{}, std::make_index_sequence<sizeof...(Ts) - N>{}, f, std::forward<Ts>(args)...); 
} 

Demo

2

這個的核心是call_with_some,它需要一個可調用的和一系列的索引和可變參數,並用可變參數的索引調用可調用的。

一些指數助手:

template<size_t K, class indexes> 
struct offset_indexes; 
template<size_t K, size_t...Is> 
struct offset_indexes<K, std::index_sequence<Is...>>: 
    std::index_sequence<(K+Is)...> 
{}; 

call_with_some,SFINAE啓用。

// SFINAE test optional, but why not: 
template<class F, class...Ts, size_t... Is> 
std::result_of_t< F(std::tuple_element_t< Is, std::tuple<Ts&&...> >...) > 
call_with_some(F&& f, std::index_sequence<Is...>, Ts&&... ts) { 
    return std::forward<F>(f)(
    std::get<Is>(
     std::forward_as_tuple(std::forward<Ts>(ts)...) 
    )... 
); 
} 

現在問題的肉。 call_by_n是存儲另一個函數對象的函數對象。它採用偏移量,然後用它來調用的論點,即偏移(次nF的序列,傳遞n參數:

template<class F, size_t n> 
struct call_by_n { 
    F&& f; 
    // Offset... should be `<0, ..., sizeof...(Args)/n -1>` 
    template<size_t...Offset, class...Args> 
    void operator()(std::index_sequence<Offset...>, Args&&...args) { 
    static_assert(0==(sizeof...(Args)%n), "Number of args must be divisible by n"); 
    // <0,1,2,3,4,...,n-1> sequence: 
    using indexes = std::make_index_sequence<n>; 

    using discard=int[]; 

    // the unused array trick to expand an arbitrary call: 
    (void)discard{0,(
     (call_with_some(f, offset_indexes<Offset*n, indexes>{}, std::forward<Args>(args)...)) 
    ,void(),0)...}; 
    } 
    void operator()() {} // do nothing, naturally 
}; 

現在我們只是包裝上面在你的界面:

template<size_t n, class F, class...Args> 
void forEachNArgs(F&& f, Args&&...args) { 
    static_assert((sizeof...(Args)%n)==0, "Wrong number of arguments"); 
    call_by_n<F,n>{std::forward<F>(f)}(std::make_index_sequence<sizeof...(Args)/n>{}, std::forward<Args>(args)...); 
} 

我把forEach2Args作爲練習。

live example - 很好,沒有錯別字。

此版本現在可以進行「平坦」風格的調用,沒有無限遞歸。遞歸調用的數量不會隨着Args...n而增加。

discard詭計有點亂。我們創建一個滿零的臨時整數數組,並且作爲副作用在參數包擴展中執行任意代碼。臨時的整數數組永遠不會被讀取,也不會被它的地址所取代,所以編譯器可以消除它,因爲它永遠不會存在。

在C++ 1z中,使用,的摺疊表達式可以讓我們做類似的事情,而沒有太多的樣板或魔法。

1

這裏是什麼在C++ Now2014提出了一個變化:

#include <utility> 
#include <tuple> 
#include <cassert> 

struct type_erasure { }; 

template<class T> 
struct wrapper : type_erasure { 
    wrapper(T&& w) : w_(std::forward<T>(w)) { } 
    T&& w_; 
    decltype(auto) get() { return std::forward<T>(w_); } 
}; 

template<class T> 
wrapper<T> wrapper_for(T&& x) { 
    return { std::forward<T>(x) }; 
} 

template <typename ignore> 
struct lookup; 

template <std::size_t... ignore> 
struct lookup<std::index_sequence<ignore...>> { 
    template <typename nth> 
    static decltype(auto) 
    at_position(decltype(ignore, type_erasure())..., wrapper<nth> w, ...) { 
     return w.get(); 
    } 

    template<typename... Ts> 
    static auto 
    all_after(decltype(ignore, type_erasure())..., Ts&&... args) { 
     return std::forward_as_tuple(args.get()...); 
    } 
}; 

template<std::size_t index, typename... Args> 
auto getNth(Args&&... args) { 
    return lookup<std::make_index_sequence<index>>::at_position(
     wrapper_for(std::forward<Args>(args))... 
    ); 
} 

template<std::size_t index, typename... Args> 
auto getAllAfter(Args&&... args) { 
    return lookup<std::make_index_sequence<index + 1>>::all_after(
     wrapper_for(std::forward<Args>(args))... 
    ); 
} 

int main() 
{ 
    assert(getNth<0>(1, 2, 3) == 1); 
    assert(getNth<1>(1, 2, 3) == 2); 
    assert(getNth<2>(1, 2, 3) == 3); 

    assert(getAllAfter<2>(2, 4, 6, 8, 10) == std::make_tuple(8, 10)); 
} 
+0

這不回答OP的問題。我的意思是,它回答了問題的標題,但不是問題的主體。 – Yakk

+0

謝謝。更新。 – 0x499602D2

相關問題