Nifty Fold エクスプレッション トリック

可変引数関数が必要で、すべての引数を一緒に加算したいとします。C++17 より前では、2 つの疑似再帰関数が必要です。

template <typename H, typename ... T>
auto add(H head, T... tail)
{
    return head + add(tail...);
}

template <typename H>
auto add(H head)
{
    return head;
}

ただし、C++17 では折り畳み式が追加され、ワンライナーになりました:

template <typename H, typename ... T>
auto add(H head, T... tail)
{
    return (head + ... + tail);
    // expands to: head + tail[0] + tail[1] + ...
}

演算子の評価ルールとフォールド式を悪用する意思がある場合は、さらに多くのことができます。このブログ投稿では、便利なトリックを集めています。

可能な限り、再帰を使用する代わりにフォールド式を使用してパラメーター パックを処理する必要があります。

<オール>
  • 書くコードが少なくて済みます。
  • 複数の関数呼び出しの代わりに 1 つの式だけを使用するため、(最適化なしで) より高速なコードになります。
  • テンプレートのインスタンス化が少ないため、コンパイルが高速です。
  • 欠点は、読みにくいことが多く、何が起こっているのかを説明するために追加のコメントが必要になることです。

    パックのすべてのパラメータが同じ型の場合、 03 と書くことでそれらを初期化リストに入れることができます 、そして通常のループを使用します。ただし、代わりに折り畳み式を使用すると、無料でループ展開が得られます。これは望ましい場合もあります。

    以降のすべてのスニペットで、16 変数パック 26 です。 パックの各要素を取得できる関数で、30 各パックの述語です。44 および 59 リテラル関数である必要はありません。一度に 1 つの要素を使用する任意の式にすることができます。

    コンパイラ エクスプローラーですべての例を試すことができます:https://godbolt.org/z/8fMde5d81

    リストに追加してほしい別のトリックがある場合は、お知らせください。

    各要素で関数を呼び出す

    擬似コード:

    for (auto elem : ts)
        f(elem);
    

    折りたたみ式:

    (f(ts), ...);
    // expands to: f(ts[0]), f(ts[1]), f(ts[2]), ...
    

    各要素で関数を呼び出し、コンマ演算子を折り畳みます。結果の式は、左から右、つまり順番に評価されることが保証されています。

    各要素を逆順で関数を呼び出す

    擬似コード:

    for (auto elem : reversed(ts))
        f(elem);
    

    折りたたみ式:

    int dummy;
    (dummy = ... = (f(ts), 0));
    // expands to: dummy = ((f(ts[0]), 0) = (f(ts[1]), 0)) = ...
    

    関数を逆に呼び出すには、引数を右から左に評価する演算子が必要です。そのような演算子は 60 です。 :79 、最初に 81 を評価します 、次に 92 、そして 105 .したがって、関数呼び出しの結果をいくつかの 119 にマッサージします コンマ演算子を使用して値を代入し、代入としてダミー変数に折り畳みます。最終的に大きな代入式になり、各オペランドが最初に関数を呼び出し、次に 129 になります。 、逆の順序で評価されます。

    述語が一致するまで、各要素で関数を呼び出します

    擬似コード:

    for (auto elem : ts)
    {
        if (pred(elem))
            break;
        f(elem);
    }
    

    折りたたみ式:

    ((pred(ts) ? false : (f(ts), true)) && ...);
    // expands to: (pred(ts[0]) ? false : (f(ts[0]), true))
    //              && (pred(ts[1]) ? false : (f(ts[1]), true))
    //              && ...
    

    各要素で述語を呼び出します。それが true を返す場合、結果は false になります。それ以外の場合は、関数を呼び出して true になります。次に、131 を使用して折りたたみます。 、左から右に評価され、最初の偽の結果で停止します。述語が一致したとき。

    143 のブランチを交換することによって -式、述語が一致している間に呼び出すことができます.

    述語に一致する要素があるかどうかを確認します

    擬似コード:

    for (auto elem : ts)
      if (pred(elem))
          return true;
    return false;
    

    折りたたみ式:

    bool any_of = (pred(ts) || ...);
    // expands to: pred(ts[0]) || pred(ts[1]) || ...
    

    述語呼び出しを 152 で折り畳みます 、いずれかの述語が true を返した場合に true を返します。168 左から右へのショートサーキットから評価されるため、1 つの要素が true を返した後に述語が呼び出されることはありません。

    171 で 、すべての要素が一致するかどうかを確認できます。

    述語に一致する要素の数を数える

    擬似コード:

    std::size_t count = 0;
    for (auto elem : ts)
      if (pred(elem))
          ++count;
    

    折りたたみ式:

    auto count = (std::size_t(0) + ... + (pred(ts) ? 1 : 0));
    // expands to: std::size_t(0) + (pred(ts[0]) ? 1 : 0)
    //                            + (pred(ts[1]) ? 1 : 0)
    //                            + ...
    

    各要素を 184 に変換します または 198 、述語に一致するかどうかによって異なります。次に、初期値 202 ですべてを合計します

    述語に一致する最初の要素を見つける

    擬似コード:

    for (auto elem : ts)
    {
        if (pred(elem))
            return elem;
    }
    /* not found */
    

    折りたたみ式:

    std::common_type_t<decltype(ts)...> result;
    bool found = ((pred(ts) ? (result = ts, true) : false) || ...);
    // expands to: (pred(ts[0]) ? (result = ts[0], true) : false)
    //          || (pred(ts[1]) ? (result = ts[1], true) : false)
    //          || ...
    

    これは、すべての 211 の場合にのみ機能します デフォルトで構築可能な共通の型を持っています。

    各要素をチェックし、見つかった場合は変数に格納し、結果は true になります。述語と一致しない場合は false になります。その後、229 を折り畳みます。 、左から右に評価し、最初の真の結果、つまり要素が見つかったときに停止します。

    n 番目の要素を取得します (n はランタイム値です)

    擬似コード:

    ts[n]
    

    折りたたみ式:

    std::common_type_t<decltype(ts)...> result;
    std::size_t i = 0;
    ((i++ == n ? (result = ts, true) : false) || ...);
    // expands to: (i++ == n ? (result = ts[0], true) : false)
    //          || (i++ == n ? (result = ts[1], true) : false)
    //          || ..
    

    これは、すべての 235 の場合にのみ機能します デフォルトで構築可能な共通の型を持っています。

    要素ごとにインクリメントする現在のインデックスを記憶します。目的のインデックスに到達したら、要素を記憶し、true を返します。それ以外の場合は、何もせず、false を返します。その後、247<をフォールドします。 /コード> 、左から右に評価し、最初の真の結果で停止します。つまり、目的のインデックスで要素が見つかったときです。

    無効なインデックス 257 が与えられた場合 、 266 デフォルトの構築値になります。

    最初の要素を取得

    擬似コード:

    ts[0]
    

    折りたたみ式:

    std::common_type_t<decltype(ts)...> result;
    ((result = ts, true) || ...);
    // expands to: (result = ts[0], true)
    //          || (result = ts[1], true)
    //          || ...
    

    これは、すべての 277 の場合にのみ機能します デフォルトで構築可能な共通の型を持っています。

    各要素を 284 に格納します その結果は true になります。その後、293 をフォールドします。 、左から右に評価し、最初の真の結果で停止します。つまり、最初の代入の直後です。

    パックが空の場合、304 デフォルトの構築値になります。

    最後の要素を取得

    擬似コード:

    ts[ts.size() - 1]
    

    折りたたみ式:

    auto result = (ts, ...);
    // expands to: ts[0], ts[1], ...
    

    コンマ演算子を使用してすべての要素を折りたたむだけです。その結果は最後の式、つまり最後の要素です。

    パックが空の場合、310 としてコンパイル エラーが発生します。 323 になります .

    最小限の要素を取得

    擬似コード:

    auto min = ts[ts.size() - 1];
    for (auto elem : ts)
        if (elem < min)
            min = elem;
    

    折りたたみ式:

    auto min = (ts, ...);
    ((ts < min ? min = ts, 0 : 0), ...);
    // expands to: (ts[0] < min ? min = ts[0], 0 : 0),
    //             (ts[1] < min ? min = ts[1], 0 : 0),
    //             ...
    

    これは、すべての 333 の場合にのみ機能します

    最小値を最終値に設定し、それぞれを最小値と比較します。小さい場合は、最小値を更新します。346 356 のもう一方のブランチに何らかの式があるので、 .

    通常、アルゴリズムは最初の値を最小値として開始します。ただし、パックの最後の値を取得する方が簡単なので、代わりにそれを行います。