2017-09-22 4 views
3

実行時の条件によっては、範囲を超えて直接または逆の順序で反復処理する状況が定期的に発生します。これは、典型的には、コードの重複(最初のもの)を含むか、または(1秒)に非効率的である以下ブースト範囲アダプタの実装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); 

ようなコードになります。

私は(hereから開始)、それを自分で実装することができ、私は

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

を書くことができるように、条件付きで、範囲を逆でしょう架空のブースト範囲アダプターについて何度も考えてきたが、私はないです進める方法について確かめてください。実行時の条件をサポートするためには、反復方向を決定するために各反復でブール値をチェックするか、反復コードにディスパッチするために仮想性を使用する必要があります。なぜこれがブースト範囲アダプタで提供されていないのですか?

代替ソリューションはありますか?

答えて

1

各インクリメントで実行時のチェックを避けたい場合は、実行時の値をループ構造外のコンパイル時の値に変換する必要があります。

この場合、私たちはループしている範囲を変化させたい、ボディは変化させたくない。

easyこれを行うには、ボディのラムダを書いてから、選択するループを選択するスイッチを用意します。

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); 
} 

私たちはループ外でランタイムディスパッチを行い、どのようにループするかについての静的なタイプの情報を持つ2つの異なるループを作成しました。

私たちは、このようなアダプタを作ることができます。

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

magic_switchはその最初の引数としてインデックス(std::size_t)をとります。それは引数のリストを取るラムダを返します。これはラムダを返し、ラムダを受け取り、そのリストに最初の引数のインデックスによって決定されるように、2番目のリストから引数を渡します。

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)); 
    }; 
    }; 
} 

は実装時のスケッチです(ほとんどの場合、ビルドエラーが含まれています)。

難しい部分は、「タイプフロー」(用語をコインにする)が、通常はそれが欲しいとは思わない方法です。だから、私は基本的に連続通行スタイルを使わなければならない。

多くのコンパイラはラムダ全体を含むパック展開に不満を持っていることに注意してください。関数ポインタを返すヘルパー関数を書くことができます。

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)); 
    }; 
} 

、その後でテーブルを置き換えます。これらのコンパイラで

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

+0

うわー!ありがとうございました!アイデアが得られますが、私はあなたの魔法のスイッチの実装の詳細を理解しようとします。 'decltype(range)(range)'のポイントは何ですか?これはキャストですか、なぜそれが必要ですか? –

+1

@ChristopheFuzier 'auto &&'リファレンスのために、それは完全な転送を行います。 "それが宣言された型としての範囲"と読んでください。これは 'std :: forward (range)'に相当しますが、 'range'の型が' auto && 'または' T && '(forwarding reference)である限り半分です。 'range'が値型(' auto'または 'T')であれば動作しません。 – Yakk

+0

@ChristopheFuzierまた、 'magic_switch'は代わりに' variant'sに基づいて動作することに注意してください。 – Yakk

関連する問題