2017-09-22 90 views
3

我經常遇到代碼中的情況,我想根據運行時情況以直接或相反的順序遍歷範圍。這通常導致在如下所示的代碼實現升壓範圍適配器reversed_if

if (reverse) { 
    using boost::adaptors::reversed; 
    for (auto const & x : range | reversed) do_stuff(x); 
} else { 
    for (auto const & x : range) do_stuff(x); 
} 

std::vector<Foo> v(range.begin(), range.end()); 
if (reverse) boost::range::reverse(v); 
for (auto const & x : v) do_stuff(x); 

包含代碼重複(第一個)或是低效的(第二個)。

我一直在思考很多次關於假設的升壓範圍適配器,將有條件地扭轉一個範圍,這樣我就可以寫

using boost::adaptors::reversed_if; 
for (auto const & x : range | reversed_if(reverse)) do_stuff(x); 

我可以實現它自己(從here開始),但我不確定如何繼續。爲了支持運行時條件,恐怕我必須在每次迭代時檢查布爾值以確定迭代方向或使用虛擬性來分派迭代代碼。這是Boost系列適配器沒有提供的原因嗎?

任何替代解決方案?

回答

1

如果要避免在每個增量的運行時檢查哪條路,您必須將運行時值轉換爲循環結構外的編譯時間值。

在這種情況下,我們希望我們循環的範圍有所不同,而身體則不會。

easy這樣做的方法是爲身體寫一個lambda,然後有一個開關來選擇要選擇的循環。

auto do_stuff = [&](auto&& elem){ /* code */ }; 
if (reverse) { 
    using boost::adaptors::reversed; 
    for (auto const & x : range | reversed) do_stuff(x); 
} else { 
    for (auto const & x : range) do_stuff(x); 
} 

我們已經完成了運行調度循環外,創造他們如何循環靜態類型信息的兩個不同的循環。

我們可以做一個適配器是這樣的:

magic_switch 
    (reverse) 
    (range, range|reversed) 
    (
    [&](auto&& range){ 
     for (auto const& x : decltype(range)(range)) { 
     do_stuff(x); 
     } 
    } 
); 

其中magic_switch的Index(std::size_t)作爲第一個參數。它返回一個lambda,它接受一個參數列表。它返回一個lambda表達式,該lambda表達式接受一個lambda表達式並將第二個列表中的參數傳遞給它,如第一個參數索引確定的那樣。

inline auto magic_switch(std::size_t I) { 
    return [I](auto&&...options) { 
    return [I, &](auto&& f)->decltype(auto) { 
     using fptr = void(*)(void const volatile* op, decltype(f)); 
     static const fptr table[] = { 
     +[](void const volatile* op_in, decltype(f) f) { 
      auto* option = static_cast<std::decay_t<decltype(options)>*>(op_in); 
      decltype(f)(f)(decltype(options)(*option)); 
     }... 
     }; 
     const volatile void* ptrs[] = {std::addressof(options)...}; 
     if (I >= sizeof...(options)) I = sizeof...(options)-1; 
     if (I == -1) return; 
     table[I](ptrs[I], decltype(f)(f)); 
    }; 
    }; 
} 

是實現中的草圖(它幾乎肯定包含構建錯誤)。

困難的部分是「類型流動」(投幣一詞)並不像你通常想要的那樣。所以我基本上被迫使用延續傳球風格。

請注意,許多編譯器對包含整個lambda的包擴展不滿意。返回函數指針輔助函數可以寫成:在這些編譯器

 static const fptr table[] = { 
     get_fptr<decltype(options), decltype(f)>()... 
     }; 

template<class F> 
using f_ptr = void(*)(const volatile void*, F&&); 

template<class Option, class F> 
f_ptr<F> get_f_ptr() { 
    return +[](void const volatile* op_in, F&& f) { 
    auto* option = static_cast<std::decay_t<Option>*>(op_in); 
    std::forward<F>(f)(std::forward<Option>(*option)); 
    }; 
} 

然後用替換表。

+0

哇!謝謝!我明白了,但我會嘗試瞭解魔術開關的實施細節。 「decltype(範圍)(範圍)」的含義是什麼?這是一個演員,爲什麼需要? –

+1

@ChristopheFuzier對於'auto &&'參考,它可以完美轉發。閱讀它作爲「範圍作爲它聲明的類型」。它相當於'std :: forward (range)',但只有'range'是'auto &&'或'T &&'(轉發引用)類型的一半長。如果'range'是一個值類型('auto'或'T'),它不起作用。 – Yakk

+0

@ChristopheFuzier另外請注意,'magic_switch'可以基於變體工作。 – Yakk