可変個のテンプレートと関数を学ぼうとしています。このコードがコンパイルされない理由がわかりません:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args)
{
(bar(args)...);
}
int main()
{
foo2(1, 2, 3, "3");
return 0;
}
コンパイルすると、エラーで失敗します:
エラーC3520: 'args':このコンテキストではパラメーターパックを展開する必要があります
(関数foo2
)。
パックの展開が発生する可能性のある場所の1つは、braced-init-listの中です。これを利用するには、ダミー配列の初期化リスト内に展開を配置します。
template<typename... Args>
static void foo2(Args &&... args)
{
int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
}
初期化子の内容をより詳細に説明するには:
{ 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
| | | | |
| | | | --- pack expand the whole thing
| | | |
| | --perfect forwarding --- comma operator
| |
| -- cast to void to ensure that regardless of bar()'s return type
| the built-in comma operator is used rather than an overloaded one
|
---ensure that the array has at least one element so that we don't try to make an
illegal 0-length array when args is empty
デモ 。
{}
で拡張することの重要な利点は、左から右への評価が保証されることです。
C++ 17 fold expression では、次のように書くことができます
((void) bar(std::forward<Args>(args)), ...);
パラメータパックは、厳密に定義されたコンテキストのリストでのみ展開でき、演算子,
はそれらの1つではありません。つまり、パック拡張を使用して、演算子,
で区切られた一連の部分式で構成される式を生成することはできません。
経験則は、「拡張により、,
がリスト区切り文字である,
-で区切られたパターンのリストを生成できます。」です。演算子,
は、文法的な意味でリストを作成しません。
引数ごとに関数を呼び出すには、再帰(可変引数テンプレートプログラマーボックスの主要ツール)を使用できます。
template <typename T>
void bar(T t) {}
void foo2() {}
template <typename Car, typename... Cdr>
void foo2(Car car, Cdr... cdr)
{
bar(car);
foo2(cdr...);
}
int main()
{
foo2 (1, 2, 3, "3");
}
パラメータパックは、厳密に定義されたコンテキストのリストでのみ展開でき、演算子,
はそれらの1つではありません。つまり、パック拡張を使用して、演算子,
で区切られた一連の部分式で構成される式を生成することはできません。
経験則では、「拡張により,
で区切られたパターンのリストを生成できます。,
はリスト区切り文字です。」演算子,
は、文法的な意味ではリストを作成しません。
引数ごとに関数を呼び出すには、再帰(可変引数テンプレートプログラマーボックスの主要ツール)を使用できます。
#include <utility>
template<typename T>
void foo(T &&t){}
template<typename Arg0, typename Arg1, typename ... Args>
void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
foo(std::forward<Arg0>(arg0));
foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}
auto main() -> int{
foo(1, 2, 3, "3");
}
便利な非コピー情報
この回答でおそらく見たことがない別のことは、&&
指定子とstd::forward
の使用です。 C++では、&&
指定子は、右辺値参照または汎用参照の2つのことのいずれかを意味します。
私は右辺値参照には触れませんが、可変個引数テンプレートで作業している人には。普遍的な参照は神からの送信です。
完全な転送
std::forward
および汎用参照の使用の1つは、型を他の関数に完全に転送することです。
あなたの例では、int&
をfoo2
に渡すと、テンプレートの推論後、生成されたfoo2
関数の署名のために、自動的にint
に降格されます。このarg
を参照によって変更する別の関数に転送したい場合、foo2
が一時的に作成された参照を渡すため、望ましくない結果が得られます(変数は変更されません) int
を渡すことにより。これを回避するために、anyタイプのreference変数(rvalueorlvalue)次に、転送関数で渡された正確な型を確実に渡すために、std::forward
を使用し、次にonlyを使用して、型の降格。私たちは今、それが最も重要なポイントにいるからです。
必要な場合は、 ユニバーサルリファレンス および 完全な転送 を参照してください。 Scott Meyersはリソースとして非常に優れています。
これに対するC++ 17ソリューションは、予想されるコードに非常に近いものです。
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args) {
(bar(args), ...);
}
int main() {
foo2(1, 2, 3, "3");
return 0;
}
すべての式の間にカンマ演算子を使用してパターンを展開します
// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));
展開によって生成されたmake_Tuple
シーケンスが有効なコンテキストを導入するため、パックの展開に,
を使用できます
make_Tuple( (bar(std::forward<Args>(args)), 0)... );
今、私は生成されたゼロの未使用/名前なし/一時的なタプルがコンパイラによって検出され、最適化されて削除されたと考えています。
これは、ここの答えに基づいた完全な例です。
JavaScriptで見られるconsole.log
を再現する例:
Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");
ファイル名js_console.h
:
#include <iostream>
#include <utility>
class Console {
protected:
template <typename T>
void log_argument(T t) {
std::cout << t << " ";
}
public:
template <typename... Args>
void log(Args&&... args) {
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void warn(Args&&... args) {
cout << "WARNING: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
template <typename... Args>
void error(Args&&... args) {
cout << "ERROR: ";
int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
cout << endl;
}
};