サーチ…
構文
- [ default-capture 、 capture-list ]( 引数リスト )変更可能なスロー指定 属性 - > return-type { lambda-body } //ラムダ指定子と属性の順序。
- [ キャプチャリスト ]( 引数リスト ){ lambda-body } //共通ラムダ定義。
- [=]( 引数リスト ){ lambda-body } //必要なすべてのローカル変数を値で取得します。
- [&]( 引数リスト ){ lambda-body } //必要なすべてのローカル変数を参照によって取得します。
- [ capture-list ] { lambda-body } //引数リストと指定子を省略することができます。
パラメーター
パラメータ | 詳細 |
---|---|
デフォルトキャプチャ | リストされていないすべての変数のキャプチャ方法を指定します。可能= (値によってキャプチャ)または& (参照によってキャプチャ)。省略した場合、リストされていない変数はラムダ本体内でアクセスできなくなります。 デフォルトキャプチャは、 キャプチャリストに先行する必要があります 。 |
キャプチャリスト | ラムダ本体内でローカル変数にアクセスする方法を指定します。接頭辞のない変数は値で取り込まれます。 & 接頭辞が付いた変数は参照によって取得されます。クラスメソッド内では、 this を使用して、すべてのメンバーを参照可能にすることができます。リストにデフォルトキャプチャが先行されていない限り、リストされていない変数にはアクセスできません。 |
引数リスト | ラムダ関数の引数を指定します。 |
変更可能な | (オプション)通常、値によって取り込まれる変数はconst です。 mutable を指定すると、非constになります。これらの変数に対する変更は、呼び出し間で保持されます。 |
スロー仕様 | (オプション)ラムダ関数の例外スローイング動作を指定します。たとえば、 noexcept またはthrow(std::exception) です。 |
属性 | (オプション)ラムダ関数の任意の属性。たとえば、 lambda-bodyが常に例外をスローした場合、 [[noreturn]] を使用できます。 |
- > 戻り値型 | (オプション)ラムダ関数の戻り値の型を指定します。戻り値の型をコンパイラが判別できない場合に必要です。 |
ラムダボディ | ラムダ関数の実装を含むコードブロック。 |
備考
C ++ 17(現在のドラフト)では、基本的にコンパイル時に評価できるlambdaというconstexpr
lambdaが導入されています。 lambdaはconstexpr
要件を満たしていれば自動的にconstexpr
ですが、 constexpr
キーワードを使用して指定することもできます。
//Explicitly define this lambdas as constexpr
[]() constexpr {
//Do stuff
}
ラムダ式とは何ですか?
ラムダ式は簡単な関数オブジェクトを作成する簡潔な方法を提供します。ラムダ式は、その結果オブジェクトが関数オブジェクトのように振る舞うクロージャオブジェクトと呼ばれる値である。
名前「ラムダ式」はラムダ計算から始まります。 ラムダ計算は、論理と計算能力についての質問を調べるために、1930年にアロンゾ教会によって発明された数学的形式論です。ラムダ計算は、関数型プログラミング言語LISPの基礎を形成しました。ラムダ計算とLISPと比較して、C ++のラムダ式は名前のないプロパティを共有し、周囲のコンテキストから変数を取得しますが、関数を操作して関数を返す機能がありません。
ラムダ式は、呼び出し可能なオブジェクトを取る関数の引数としてよく使われます。これは、引数として渡されたときにのみ使用される名前付き関数を作成するよりも簡単になります。そのような場合、関数式オブジェクトをインラインで定義できるので、ラムダ式が一般に好まれます。
ラムダは通常、キャプチャリスト[]
、オプションのパラメータリスト()
、およびボディ{}
の3つの部分で構成され、これらはすべて空にすることができます。
[](){} // An empty lambda, which does and returns nothing
キャプチャリスト
[]
はキャプチャリストです。デフォルトでは、囲みスコープの変数にラムダでアクセスすることはできません。変数をキャプチャすると、ラムダ内でコピーまたは参照 としてアクセスできるようになります 。キャプチャされた変数はラムダの一部になります。関数の引数とは対照的に、ラムダを呼び出すときに渡す必要はありません。
int a = 0; // Define an integer variable
auto f = []() { return a*9; }; // Error: 'a' cannot be accessed
auto f = [a]() { return a*9; }; // OK, 'a' is "captured" by value
auto f = [&a]() { return a++; }; // OK, 'a' is "captured" by reference
// Note: It is the responsibility of the programmer
// to ensure that a is not destroyed before the
// lambda is called.
auto b = f(); // Call the lambda function. a is taken from the capture list and not passed here.
パラメータリスト
()
はパラメータリストで 、通常の関数とほぼ同じです。ラムダが引数を取らない場合は、これらのカッコを省略することができます(ラムダmutable
を宣言する必要がある場合を除く)。これらの2つのラムダは同等です:
auto call_foo = [x](){ x.foo(); };
auto call_foo2 = [x]{ x.foo(); };
パラメータリストでは、実際の型の代わりにプレースホルダ型auto
使用できます。そうすることで、この引数は関数テンプレートのテンプレートパラメータのように動作します。ラムダに続くのは、ジェネリックコードでベクトルをソートする場合には同等です:
auto sort_cpp11 = [](std::vector<T>::const_reference lhs, std::vector<T>::const_reference rhs) { return lhs < rhs; };
auto sort_cpp14 = [](const auto &lhs, const auto &rhs) { return lhs < rhs; };
機能体
{}
は本体で 、通常の関数と同じです。
ラムダの呼び出し
ラムダ式の結果オブジェクトはクロージャであり、 operator()
を使用して呼び出すことができます(他の関数オブジェクトと同様)。
int multiplier = 5;
auto timesFive = [multiplier](int a) { return a * multiplier; };
std::out << timesFive(2); // Prints 10
multiplier = 15;
std::out << timesFive(2); // Still prints 2*5 == 10
戻り値の型
デフォルトでは、ラムダ式の戻り値の型が導出されます。
[](){ return true; };
この場合、戻りbool
型はbool
です。
次の構文を使用して、戻り値の型を手動で指定することもできます。
[]() -> bool { return true; };
変更可能なラムダ
ラムダの値によって捕捉されたオブジェクトは、デフォルトでは不変です。これは、生成されたクロージャオブジェクトのoperator()
がデフォルトでconst
であるためです。
auto func = [c = 0](){++c; std::cout << c;}; // fails to compile because ++c
// tries to mutate the state of
// the lambda.
mutable
というキーワードを使用すると、より近いオブジェクトのoperator()
const
変更することができます。
auto func = [c = 0]() mutable {++c; std::cout << c;};
戻り値の型とともに使用すると、その前にmutable
が来ます。
auto func = [c = 0]() mutable -> int {++c; std::cout << c; return c;};
ラムダの有用性を示す例
C ++ 11以前:
// Generic functor used for comparison
struct islessthan
{
islessthan(int threshold) : _threshold(threshold) {}
bool operator()(int value) const
{
return value < _threshold;
}
private:
int _threshold;
};
// Declare a vector
const int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> vec(arr, arr+5);
// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
std::vector<int>::iterator it = std::find_if(vec.begin(), vec.end(), islessthan(threshold));
C ++ 11以降:
// Declare a vector
std::vector<int> vec{ 1, 2, 3, 4, 5 };
// Find a number that's less than a given input (assume this would have been function input)
int threshold = 10;
auto it = std::find_if(vec.begin(), vec.end(), [threshold](int value) { return value < threshold; });
戻り値の型の指定
単一のreturn文を持つラムダ、または同じ型の式を持つ複数のreturn文の場合、コンパイラは戻り値の型を推定できます。
// Returns bool, because "value > 10" is a comparison which yields a Boolean result
auto l = [](int value) {
return value > 10;
}
異なる型の複数のreturn文を持つlambdaの場合、コンパイラは戻り型を推論できません:
// error: return types must match if lambda has unspecified return type
auto l = [](int value) {
if (value < 10) {
return 1;
} else {
return 1.5;
}
};
この場合、戻り値の型を明示的に指定する必要があります。
// The return type is specified explicitly as 'double'
auto l = [](int value) -> double {
if (value < 10) {
return 1;
} else {
return 1.5;
}
};
このルールは、 auto
タイプ控除のルールと一致します。明示的に指定された戻り値の型を持たないLambdaは参照を返すことはないので、参照型を望む場合は明示的に指定する必要があります:
auto copy = [](X& x) { return x; }; // 'copy' returns an X, so copies its input
auto ref = [](X& x) -> X& { return x; }; // 'ref' returns an X&, no copy
値で取り込む
キャプチャリストに変数の名前を指定すると、ラムダは値でキャプチャします。これは、ラムダ用に生成されたクロージャタイプが変数のコピーを格納することを意味します。これはまた、変数の型をコピーコンストラクタブルにする必要があります。
int a = 0;
[a]() {
return a; // Ok, 'a' is captured by value
};
auto p = std::unique_ptr<T>(...);
[p]() { // Compile error; `unique_ptr` is not copy-constructible
return p->createWidget();
};
C ++ 14以降では、その場で変数を初期化することができます。これにより、移動のみのタイプをラムダに取り込むことができます。
auto p = std::make_unique<T>(...);
[p = std::move(p)]() {
return p->createWidget();
};
ラムダは名前で変数を渡すときに変数を値で取り込みますが、デフォルトではその変数をラムダ本体内で変更することはできません。これは、closure型がラムダ本体をoperator() const
宣言に入れるためです。
const
は、クロージャ型のメンバ変数へのアクセスに適用され、クロージャのメンバであるキャプチャされた変数(逆のすべての外観)に適用されます。
int a = 0;
[a]() {
a = 2; // Illegal, 'a' is accessed via `const`
decltype(a) a1 = 1;
a1 = 2; // valid: variable 'a1' is not const
};
const
を削除するには、ラムダにキーワードmutable
を指定する必要があります:
int a = 0;
[a]() mutable {
a = 2; // OK, 'a' can be modified
return a;
};
a
は値によって取得されたためa
ラムダを呼び出すことによって行われた変更はa
影響a
ません。 a
の値は構築時にラムダにコピーされたので、ラムダのa
のコピーは外部a
変数とは別です。
int a = 5 ;
auto plus5Val = [a] (void) { return a + 5 ; } ;
auto plus5Ref = [&a] (void) {return a + 5 ; } ;
a = 7 ;
std::cout << a << ", value " << plus5Val() << ", reference " << plus5Ref() ;
// The result will be "7, value 10, reference 12"
一般化されたキャプチャ
Lambdaは変数だけでなく、式を取り込むことができます。これにより、lambdaは移動専用の型を格納できます。
auto p = std::make_unique<T>(...);
auto lamb = [p = std::move(p)]() //Overrides capture-by-value of `p`.
{
p->SomeFunc();
};
これは、外側に移動しp
ラムダキャプチャ変数に変数を、とも呼ばれp
。 lamb
今make_unique
によって割り当てられたメモリを所有しています。クロージャーにはコピー不可能な型が含まれているので、 lamb
自体はコピーできません。しかし、それは移動することができます:
auto lamb_copy = lamb; //Illegal
auto lamb_move = std::move(lamb); //legal.
今lamb_move
はメモリを所有しています。
std::function<>
は、格納された値をコピー可能にする必要があることに注意してください。独自のmove-only- std::function
必要とするstd::function
を書くこともできますし、ラムダをshared_ptr
ラッパーに入れるだけでもかまいません。
auto shared_lambda = [](auto&& f){
return [spf = std::make_shared<std::decay_t<decltype(f)>>(decltype(f)(f))]
(auto&&...args)->decltype(auto) {
return (*spf)(decltype(args)(args)...);
};
};
auto lamb_shared = shared_lambda(std::move(lamb_move));
私達の移動のみのラムダを取り、コピーされ、その後に格納することができるラムダ戻り、共有ポインタにその状態を詰め込むstd::function
または類似。
一般化されたキャプチャは、変数の型に対してauto
型減算を使用します。デフォルトでは、これらのキャプチャは値として宣言されますが、参照としても使用できます。
int a = 0;
auto lamb = [&v = a](int add) //Note that `a` and `v` have different names
{
v += add; //Modifies `a`
};
lamb(20); //`a` becomes 20.
一般化キャプチャでは、外部変数をキャプチャする必要はありません。任意の式を取り込むことができます:
auto lamb = [p = std::make_unique<T>(...)]()
{
p->SomeFunc();
}
これは、lambdaに外部的に宣言することなく、lambdaに保持して変更可能な任意の値を与えるのに便利です。もちろん、ラムダが作業を完了した後にこれらの変数にアクセスするつもりがない場合にのみ便利です。
リファレンスによるキャプチャ
ローカル変数の名前の前に&
つけると、変数は参照によって取り込まれます。概念的には、これは、ラムダのクロージャタイプが、ラムダのスコープの外側からの対応する変数への参照として初期化された参照変数を持つことを意味します。ラムダボディで変数を使用すると、元の変数が参照されます。
// Declare variable 'a'
int a = 0;
// Declare a lambda which captures 'a' by reference
auto set = [&a]() {
a = 1;
};
set();
assert(a == 1);
mutable
というキーワードは必要ではありません。なぜならa
自身はconst
はないからです。
もちろん、参照によって取得することは、ラムダがキャプチャする変数の範囲をエスケープしてはならないことを意味します。関数を呼び出す関数を呼び出すことはできますが、参照の範囲を超えてラムダを格納する関数を呼び出さないでください。そしてラムダを返さないでください。
デフォルトキャプチャ
デフォルトでは、キャプチャリストに明示的に指定されていないローカル変数には、ラムダ本体内からアクセスすることはできません。しかし、ラムダボディによって指定された変数を暗黙的に取り込むことは可能です:
int a = 1;
int b = 2;
// Default capture by value
[=]() { return a + b; }; // OK; a and b are captured by value
// Default capture by reference
[&]() { return a + b; }; // OK; a and b are captured by reference
明示的なキャプチャは暗黙的なデフォルトのキャプチャと並行して実行できます。明示的なキャプチャ定義は、デフォルトのキャプチャよりも優先されます。
int a = 0;
int b = 1;
[=, &b]() {
a = 2; // Illegal; 'a' is capture by value, and lambda is not 'mutable'
b = 2; // OK; 'b' is captured by reference
};
一般的なラムダ
ラムダ関数は、任意の型の引数を取ることができます。これによりラムダをより一般的にすることができます:
auto twice = [](auto x){ return x+x; };
int i = twice(2); // i == 4
std::string s = twice("hello"); // s == "hellohello"
これは、クロージャー型のoperator()
テンプレート関数にオーバーロードさせることによってC ++で実装されています。次の型は、上記のラムダクロージャと同じ動作をします。
struct _unique_lambda_type
{
template<typename T>
auto operator() (T x) const {return x + x;}
};
ジェネリックラムダのすべてのパラメータが汎用である必要はありません。
[](auto x, int y) {return x + y;}
ここで、 x
は最初の関数の引数に基づいて導かれ、 y
は常にint
ます。
一般的なラムダは、 auto
と&
通常の規則を使用して、参照によっても引数を取ることができます。ジェネリックパラメータがauto&&
と取られた場合、これは渡された引数への転送参照であり、 rvalue参照ではありません。
auto lamb1 = [](int &&x) {return x + 5;};
auto lamb2 = [](auto &&x) {return x + 5;};
int x = 10;
lamb1(x); // Illegal; must use `std::move(x)` for `int&&` parameters.
lamb2(x); // Legal; the type of `x` is deduced as `int&`.
ラムダ関数は可変であり、引数を完全に転送することができます:
auto lam = [](auto&&... args){return f(std::forward<decltype(args)>(args)...);};
または:
auto lam = [](auto&&... args){return f(decltype(args)(args)...);};
auto&&
タイプの変数で「正しく」動作します。
ジェネリックlambdaを使用する強い理由は、構文を訪問するためです。
boost::variant<int, double> value;
apply_visitor(value, [&](auto&& e){
std::cout << e;
});
ここでは、多態的な方法で訪問しています。他の文脈では、渡している型の名前は面白くない:
mutex_wrapped<std::ostream&> os = std::cout;
os.write([&](auto&& os){
os << "hello world\n";
});
std::ostream&
タイプを繰り返すとここにノイズがあります。あなたがそれを使うたびに変数の型を言わなければならないようなものです。ここではビジターを作成していますが、多態的なものは作成していません。 auto
あなたが使用する可能性があります同様の理由で使用されているauto
中でfor(:)
ループ。
関数ポインタへの変換
ラムダのキャプチャリストが空の場合、ラムダは、同じ引数をとり、同じ戻り値の型を返す関数ポインタへの暗黙的な変換を行います。
auto sorter = [](int lhs, int rhs) -> bool {return lhs < rhs;};
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // implicit conversion
このような変換は、単項プラス演算子を使用して実行することもできます。
func_ptr sorter_func2 = +sorter; // enforce implicit conversion
この関数ポインタを呼び出すと、ラムダでoperator()
を呼び出すのとまったく同じように動作します。この関数ポインタは、ソースラムダクロージャの存在に決して依存しません。したがって、ラムダ閉鎖よりも長く存続する可能性があります。
この機能は主に、C ++関数オブジェクトではなく関数ポインタを扱うAPIでlambdaを使用する場合に便利です。
関数ポインタへの変換は、空のキャプチャリストを持つ汎用ラムダに対しても可能です。必要に応じて、適切な専門分野を選択するためにテンプレート引数の控除が使用されます。
auto sorter = [](auto lhs, auto rhs) { return lhs < rhs; };
using func_ptr = bool(*)(int, int);
func_ptr sorter_func = sorter; // deduces int, int
// note however that the following is ambiguous
// func_ptr sorter_func2 = +sorter;
クラスラムダとこれのキャプチャ
クラスのメンバ関数で評価されるラムダ式は暗黙のうちにそのクラスの友人です:
class Foo
{
private:
int i;
public:
Foo(int val) : i(val) {}
// definition of a member function
void Test()
{
auto lamb = [](Foo &foo, int val)
{
// modification of a private member variable
foo.i = val;
};
// lamb is allowed to access a private member, because it is a friend of Foo
lamb(*this, 30);
}
};
そのようなラムダは、そのクラスの友人であるだけでなく、クラス内で宣言されているクラスと同じアクセス権を持ちます。
Lambdasは、外部関数が呼び出されたオブジェクトインスタンスを表すthis
ポインタを取得できます。これは、 this
をキャプチャリストに追加することによって行われます。
class Foo
{
private:
int i;
public:
Foo(int val) : i(val) {}
void Test()
{
// capture the this pointer by value
auto lamb = [this](int val)
{
i = val;
};
lamb(30);
}
};
this
が捕捉されると、ラムダはその包含クラスのメンバー名を、その包含クラスにあるかのように使用することができる。したがって暗黙のthis->
がそのようなメンバーに適用されます。
this
は値によって取得されますが、型の値では取得されないことに注意してください。これはポインタであるthis
の値によって取得されます 。そのため、ラムダが所有していないthis
。ラムダアウトがそれを作成したオブジェクトの生存期間にわたって存在する場合、ラムダは無効になる可能性があります。
これは、ラムダが変更mutable
と宣言するthis
なくthis
を修正できることも意味します。それはオブジェクトではなく、 const
であるポインタです。つまり、外部メンバ関数自体がconst
関数でない限りです。
また、デフォルトのキャプチャ句( [=]
と[&]
両方) も this
暗黙的に取得することに注意してください。そして、両方ともポインタの値でキャプチャします。実際には、デフォルトが与えられたときにthis
をキャプチャリストに指定するのは誤りです。
ラムダは、ラムダが作成された時点で作成されたthis
オブジェクトのコピーを取得できます。これは*this
をキャプチャリストに追加することで行います:
class Foo
{
private:
int i;
public:
Foo(int val) : i(val) {}
void Test()
{
// capture a copy of the object given by the this pointer
auto lamb = [*this](int val) mutable
{
i = val;
};
lamb(30); // does not change this->i
}
};
ファンクタを使用してラムダ関数をC ++ 03に移植する
C ++のラムダ関数は、構文解析ツールを書くための非常に簡潔な構文を提供する構文的な砂糖です。したがって、ラムダ関数をファンクタに変換することによって、同等の機能をC ++ 03で得ることができます(はるかに冗長です)。
// Some dummy types:
struct T1 {int dummy;};
struct T2 {int dummy;};
struct R {int dummy;};
// Code using a lambda function (requires C++11)
R use_lambda(T1 val, T2 ref) {
// Use auto because the type of the lambda is unknown.
auto lambda = [val, &ref](int arg1, int arg2) -> R {
/* lambda-body */
return R();
};
return lambda(12, 27);
}
// The functor class (valid C++03)
// Similar to what the compiler generates for the lambda function.
class Functor {
// Capture list.
T1 val;
T2& ref;
public:
// Constructor
inline Functor(T1 val, T2& ref) : val(val), ref(ref) {}
// Functor body
R operator()(int arg1, int arg2) const {
/* lambda-body */
return R();
}
};
// Equivalent to use_lambda, but uses a functor (valid C++03).
R use_functor(T1 val, T2 ref) {
Functor functor(val, ref);
return functor(12, 27);
}
// Make this a self-contained example.
int main() {
T1 t1;
T2 t2;
use_functor(t1,t2);
use_lambda(t1,t2);
return 0;
}
ラムダ関数がmutable
あれば、ファンクタのcall-operatorを非const、すなわち:
R operator()(int arg1, int arg2) /*non-const*/ {
/* lambda-body */
return R();
}
再帰ラムダ
ユークリッドのgcd()
をラムダとして書くとしましょう。関数として、次のようになります。
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a%b);
}
しかし、ラムダは再帰的に実行することはできません。ラムダを呼び出す方法はありません。ラムダには名前がありません。ラムダの本体内でthis
を使用すると、 this
がキャプチャされたものを参照しthis
(ラムダがメンバ関数の本体で作成されていると仮定します。そうでない場合はエラーです)。では、この問題をどうやって解決するのでしょうか?
std::function
使用std::function
ラムダはまだ構築されていないstd::function
への参照を取得することができstd::function
:
std::function<int(int, int)> gcd = [&](int a, int b){
return b == 0 ? a : gcd(b, a%b);
};
これは機能しますが、控えめに使用する必要があります。それは遅いです(直接関数呼び出しの代わりにタイプ消去を使用しています)、壊れやすい( gcd
コピーするかgcd
を返すことはラムダが元のオブジェクトを参照するので中断します)、汎用ラムダでは機能しません。
2つのスマートポインタの使用:
auto gcd_self = std::make_shared<std::unique_ptr< std::function<int(int, int)> >>();
*gcd_self = std::make_unique<std::function<int(int, int)>>(
[gcd_self](int a, int b){
return b == 0 ? a : (**gcd_self)(b, a%b);
};
};
これは多くの間接参照(オーバーヘッド)を追加しますが、コピー/返すことができ、すべてのコピーは状態を共有します。それはあなたがラムダを返すことを可能にし、そうでなければ上記の解決策より脆弱ではありません。
Y-コンビネータを使用する
短いユーティリティ構造体の助けを借りて、これらの問題をすべて解決することができます:
template <class F>
struct y_combinator {
F f; // the lambda will be stored here
// a forwarding operator():
template <class... Args>
decltype(auto) operator()(Args&&... args) const {
// we pass ourselves to f, then the arguments.
// the lambda should take the first argument as `auto&& recurse` or similar.
return f(*this, std::forward<Args>(args)...);
}
};
// helper function that deduces the type of the lambda:
template <class F>
y_combinator<std::decay_t<F>> make_y_combinator(F&& f) {
return {std::forward<F>(f)};
}
// (Be aware that in C++17 we can do better than a `make_` function)
私たちはgcd
を次のように実装することができます:
auto gcd = make_y_combinator(
[](auto&& gcd, int a, int b){
return b == 0 ? a : gcd(b, a%b);
}
);
y_combinator
は、ラムダ計算のコンセプトです。定義するまで、自分の名前を付けなくても再帰が可能です。これはまさにラムダの問題です。
あなたは最初の引数として "recurse"を取るラムダを作成します。あなたが再帰したいとき、あなたは再帰の引数を渡します。
y_combinator
は、その関数をその引数で呼び出すが、適切な "recurse"オブジェクト(つまり、 y_combinator
自体)を最初の引数として持つ関数オブジェクトを返します。それはy_combinator
と呼ばれる残りの引数をラムダにも転送します。
要するに:
auto foo = make_y_combinator( [&](auto&& recurse, some arguments) {
// write body that processes some arguments
// when you want to recurse, call recurse(some other arguments)
});
重大な制限や重大なオーバーヘッドがなくラムダで再帰があります。
インラインパラメータパックのアンパックにlambdasを使用する
パラメータパックを解凍するには、伝統的に、実行するたびにヘルパー関数を記述する必要があります。
このおもちゃの例では:
template<std::size_t...Is>
void print_indexes( std::index_sequence<Is...> ) {
using discard=int[];
(void)discard{0,((void)(
std::cout << Is << '\n' // here Is is a compile-time constant.
),0)...};
}
template<std::size_t I>
void print_indexes_upto() {
return print_indexes( std::make_index_sequence<I>{} );
}
print_indexes_upto
は、インデックスのパラメータパックを作成し、展開する必要があります。これを行うには、ヘルパー関数を呼び出さなければなりません。作成したパラメータパックを展開するたびに、カスタムヘルパ関数を作成する必要があります。
これはラムダで避けることができます。
次のように、パラメータパックをラムダの呼び出しセットに展開することができます。
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
constexpr index_t<I> index{};
template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f){
using discard=int[];
(void)discard{0,(void(
f( index<Is> )
),0)...};
};
}
template<std::size_t N>
auto index_over(index_t<N> = {}) {
return index_over( std::make_index_sequence<N>{} );
}
fold式では、 index_over()
を次のように単純化できます。
template<class=void, std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f){
((void)(f(index<Is>)), ...);
};
}
これを実行したら、これを使用して、パラメータパックを手動でアンパックする代わりに、別のコードで2番目のオーバーロードを使用して、パラメータパックを "インライン"で解凍できます。
template<class Tup, class F>
void for_each_tuple_element(Tup&& tup, F&& f) {
using T = std::remove_reference_t<Tup>;
using std::tuple_size;
auto from_zero_to_N = index_over< tuple_size<T>{} >();
from_zero_to_N(
[&](auto i){
using std::get;
f( get<i>( std::forward<Tup>(tup) ) );
}
);
}
auto i
でラムダに渡さindex_over
あるstd::integral_constant<std::size_t, ???>
。これは持っているconstexpr
への変換std::size_t
の状態に依存しないthis
、私たちは、このような私たちは、それを渡す場合など一定のコンパイル時、としてそれを使用することができますstd::get<i>
の上。
上のおもちゃの例に戻るには、次のように書き直します。
template<std::size_t I>
void print_indexes_upto() {
index_over(index<I>)([](auto i){
std::cout << i << '\n'; // here i is a compile-time constant
});
}
これははるかに短く、それを使用するコードにロジックを保持します。