web-dev-qa-db-ja.com

すべての可変テンプレートの引数で関数を呼び出す方法は?

やりたい

template<typename... ArgTypes> void print(ArgTypes... Args)
{
   print(Args)...;
}

そして、それはこの非常にかさばる再帰チェーンに相当します:

template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args)
{
  print(t);
  print(Args...);
}

次に、出力したいすべてのタイプについて、明示的に単一パラメーターの特殊化を行います。

再帰的な実装の「問題」は、多くの冗長なコードが生成されることです。これは、再帰的なステップごとにN-1引数の新しい関数が生成されるためです。一方、必要なコードは、単一のN- arg print関数、および最大でN専用のprint関数。

44
rubenvb

C++ 17折りたたみ式

(f(args), ...);

オーバーロードされたコンマ演算子でオブジェクトを返す可能性があるものを呼び出す場合は、次のようにします。

((void)f(args), ...);

C++ 17以前のソリューション

ここでの典型的なアプローチは、ダムリスト初期化子を使用し、その内部で展開を行うことです。

{ print(Args)... }

カーリーイニシャライザーでは、評価の順序は左から右に保証されます。

ただし、printvoidを返すため、これを回避する必要があります。では、intにしましょう。

{ (print(Args), 0)... }

ただし、これはステートメントとしては直接機能しません。タイプを指定する必要があります。

using expand_type = int[];
expand_type{ (print(Args), 0)... };

これは、Argsパックに常に1つの要素がある限り機能します。サイズがゼロの配列は無効ですが、常に少なくとも1つの要素を持たせることで回避できます。

expand_type{ 0, (print(Args), 0)... };

このパターンをマクロで再利用可能にすることができます。

namespace so {
    using expand_type = int[];
}

#define SO_EXPAND_SIDE_EFFECTS(PATTERN) ::so::expand_type{ 0, ((PATTERN), 0)... }

// usage
SO_EXPAND_SIDE_EFFECTS(print(Args));

ただし、これを再利用可能にするには、いくつかの詳細にもう少し注意が必要です。ここでは、オーバーロードされたコンマ演算子を使用しないでください。カンマは引数voidの1つでオーバーロードできないので、それを利用しましょう。

#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
        ::so::expand_type{ 0, ((PATTERN), void(), 0)... }

あなたがいる場合 妄想 ゼロの大きな配列をゼロに割り当てるコンパイラーを恐れて、そのようにリストで初期化できるが何も格納しない他のタイプを使用できます。

namespace so {
    struct expand_type {
        template <typename... T>
        expand_type(T&&...) {}
    };
}
69

C++ 17折りたたみ式:

(f(args), ...);

シンプルなものをシンプルに保つ;-)

オーバーロードされたコンマ演算子でオブジェクトを返す可能性があるものを呼び出す場合は、次のようにします。

((void)f(args), ...);
16
Benjamin Buch

さらにシンプルで読みやすいアプローチを使用できます

template<typename... ArgTypes> void print(ArgTypes... Args)
{
   for (const auto& arg : {Args...})
   {
      print(arg);
   }
}

私は compile Explorer で両方のバリアントを試してみましたが、g3とclangの両方でO3またはO2を使用するとまったく同じコードが生成されますが、私のバリアントは明らかにクリーンです。

7
Anton Dyachenko