やりたい
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
関数。
C++ 17折りたたみ式
(f(args), ...);
オーバーロードされたコンマ演算子でオブジェクトを返す可能性があるものを呼び出す場合は、次のようにします。
((void)f(args), ...);
C++ 17以前のソリューション
ここでの典型的なアプローチは、ダムリスト初期化子を使用し、その内部で展開を行うことです。
{ print(Args)... }
カーリーイニシャライザーでは、評価の順序は左から右に保証されます。
ただし、print
はvoid
を返すため、これを回避する必要があります。では、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&&...) {}
};
}
C++ 17折りたたみ式:
(f(args), ...);
シンプルなものをシンプルに保つ;-)
オーバーロードされたコンマ演算子でオブジェクトを返す可能性があるものを呼び出す場合は、次のようにします。
((void)f(args), ...);
さらにシンプルで読みやすいアプローチを使用できます
template<typename... ArgTypes> void print(ArgTypes... Args)
{
for (const auto& arg : {Args...})
{
print(arg);
}
}
私は compile Explorer で両方のバリアントを試してみましたが、g3とclangの両方でO3またはO2を使用するとまったく同じコードが生成されますが、私のバリアントは明らかにクリーンです。