回答:
C ++には、std::for_each
やなどの便利な汎用関数が含まれておりstd::transform
、非常に便利です。残念ながら、特に適用したいファンクタが特定の関数に固有である場合、それらを使用するのは非常に面倒な場合もあります。
#include <algorithm>
#include <vector>
namespace {
struct f {
void operator()(int) {
// do something
}
};
}
void func(std::vector<int>& v) {
f f;
std::for_each(v.begin(), v.end(), f);
}
使うだけなら f
一度し、その特定の場所で、クラス全体を記述して、些細なことを1つだけ行うのはやり過ぎです。
C ++ 03では、ファンクタをローカルに保つために、次のようなものを書きたくなるかもしれません。
void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}
ただし、これは許可されてf
おらず、テンプレートに渡すことはできません、C ++ 03の関数に。
C ++ 11はラムダを導入し、インラインの匿名ファンクタを記述してを置き換えることができstruct f
ます。小さな単純な例の場合、これは読みやすく(すべてを1か所にまとめます)、たとえば最も単純な形式で維持するのが潜在的に簡単です。
void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}
ラムダ関数は、匿名ファンクタにとっては単なる構文上の砂糖です。
単純なケースでは、ラムダの戻り値の型が推測されます。例:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}
ただし、より複雑なラムダを書き始めると、戻り型がコンパイラーによって推定できない場合にすぐに遭遇します。例:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}
これを解決するために、ラムダ関数の戻り値の型を明示的に指定できます-> T
。
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) -> double {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}
これまでのところ、ラムダに渡されたもの以外は何も使用していませんが、ラムダ内で他の変数を使用することもできます。他の変数にアクセスしたい場合は[]
、これらの例ではこれまで使用されていなかった(式の)キャプチャ句を使用できます。
void func5(std::vector<double>& v, const double& epsilon) {
std::transform(v.begin(), v.end(), v.begin(),
[epsilon](double d) -> double {
if (d < epsilon) {
return 0;
} else {
return d;
}
});
}
参照と値の両方でキャプチャできます。これは、&
およびを使用して=
それぞれ指定できます。
[&epsilon]
参照によるキャプチャ[&]
参照によりラムダで使用されるすべての変数をキャプチャします[=]
ラムダで使用されるすべての変数を値でキャプチャします[&, epsilon]
[&]のような変数をキャプチャしますが、イプシロンを値で取得します[=, &epsilon]
[=]のような変数をキャプチャしますが、参照によりイプシロン生成されたものoperator()
はconst
デフォルトであり、キャプチャーがconst
デフォルトでそれらにアクセスしたときにキャプチャーが行われることを意味します。これは、同じ入力を使用する各呼び出しが同じ結果を生成するという効果がありますが、生成されるがでないことを要求するようmutable
にラムダをマークできます。operator()
const
const
常に...
()
-それはゼロ引数のラムダとして渡されますが() const
、ラムダと一致しないので、暗黙のキャストを含むそれを可能にする型変換を探します-to-function-pointer、それを呼び出す!卑劣!
std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
ただし、通常は、コンパイラーにタイプを推定させます auto f = [](int a, bool b) -> double { ... };
(そして忘れないでください#include <functional>
)
return d < 0.00001 ? 0 : d;
オペランドの1つが整数定数である場合にdoubleが返されることが保証されている理由を誰もが理解しているとは限りません(これは、?:演算子の暗黙の昇格規則が原因で、2番目と3番目のオペランドが通常の算術によって互いにバランスされています選択される変換に関係なく)。に変更する0.0 : d
と、例が理解しやすくなる可能性があります。
C ++のラムダ関数の概念は、ラムダ計算と関数型プログラミングに由来しています。ラムダは名前のない関数であり、再利用が不可能で名前を付ける価値のない短いコードスニペットに対して(理論ではなく実際のプログラミングで)役立ちます。
C ++ではラムダ関数は次のように定義されています
[]() { } // barebone lambda
またはその栄光の中で
[]() mutable -> T { } // T is the return type, still lacking throw()
[]
キャプチャリスト、()
引数リスト{}
、関数本体です。
キャプチャリストは、ラムダの外側から何をどのように関数本体内で使用できるようにするかを定義します。次のいずれかになります。
上記のいずれかをコンマ区切りのリストに混在させることができます[x, &y]
。
引数リストは、他のC ++関数と同じです。
ラムダが実際に呼び出されたときに実行されるコード。
ラムダにreturnステートメントが1つしかない場合、戻り値の型は省略でき、暗黙の型はになりdecltype(return_statement)
ます。
ラムダが変更可能とマークされている場合(例 []() mutable { }
:)、値によって取得された値をことができます。
ISO標準で定義されたライブラリは、ラムダから大きなメリットを得て、ユーザーがアクセス可能なスコープ内の小さなファンクターでコードを散らかす必要がないため、使いやすさが数バー向上します。
C ++ 14では、ラムダはさまざまな提案によって拡張されています。
キャプチャリストの要素をで初期化できるようになりました=
。これにより、変数の名前を変更したり、移動してキャプチャしたりできます。標準からの例:
int x = 4;
auto y = [&r = x, x = x+1]()->int {
r += 2;
return x+2;
}(); // Updates ::x to 6, and initializes y to 7.
とウィキペディアから取得したものでキャプチャする方法を示していますstd::move
:
auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};
ラムダをジェネリックにすることができます(周囲のスコープのどこかに型テンプレート引数がある場合は、ここauto
と同じになりT
ます
T
)。
auto lambda = [](auto x, auto y) {return x + y;};
C ++ 14では、すべての関数で推定戻り値の型が許可されており、フォームの関数に限定されませんreturn expression;
。これはラムダにも拡張されます。
r = &x; r += 2;
が、これは4の元の値に起こる
ラムダ式は通常、アルゴリズムをカプセル化して別の関数に渡せるようにするために使用されます。ただし、定義直後にラムダを実行することは可能です。
[&](){ ...your code... }(); // immediately executed lambda expression
機能的には
{ ...your code... } // simple code block
これにより、ラムダ式は複雑な関数をリファクタリングするための強力なツールになります。上記のように、コードセクションをラムダ関数でラップすることから始めます。明示的なパラメーター化のプロセスは、各ステップの後に中間テストを行って徐々に実行できます。コードブロックが完全にパラメーター化されたら(&
)、コードを外部の場所に移動して、通常の機能にすることができます。
同様に、ラムダ式を使用して、アルゴリズムの結果に基づいて変数を初期化できます...
int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!
プログラムロジックを分割する方法として、ラムダ式を引数として別のラムダ式に渡すと便利な場合もあります...
[&]( std::function<void()> algorithm ) // wrapper section
{
...your wrapper code...
algorithm();
...your wrapper code...
}
([&]() // algorithm section
{
...your algorithm code...
});
ラムダ式を使用すると、名前付きのネストされた関数を作成することもできます。これは、重複するロジックを回避する便利な方法です。名前付きラムダを使用すると、重要な関数をパラメーターとして別の関数に渡すときに、(匿名のインラインラムダと比べて)見た目が少し簡単になる傾向があります。 注:閉じ中括弧の後のセミコロンを忘れないでください。
auto algorithm = [&]( double x, double m, double b ) -> double
{
return m*x+b;
};
int a=algorithm(1,2,3), b=algorithm(4,5,6);
後続のプロファイリングによって関数オブジェクトの初期化オーバーヘッドが大幅に明らかになった場合は、これを通常の関数として書き換えることを選択できます。
if
:文はif ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
、仮定がi
あるstd::string
[](){}();
。
(lambda: None)()
構文は非常に読みやすくなっています。
main() {{{{((([](){{}}())));}}}}
答え
Q:C ++ 11のラムダ式とは何ですか?
A:内部的には、operator()constをオーバーロードした自動生成クラスのオブジェクトです。そのようなオブジェクトはクロージャーと呼ばれますとれ、コンパイラーによって作成されます。この「クロージャー」の概念は、C ++ 11のバインドの概念に近いものです。しかし、ラムダは通常、より優れたコードを生成します。また、クロージャーを介した呼び出しにより、完全なインライン化が可能になります。
Q:いつ使用しますか?
A:「シンプルで小さなロジック」を定義し、コンパイラに前の質問からの生成を実行するよう依頼します。operator()内に含めたいいくつかの式をコンパイラーに与えます。他のすべてのスタッフコンパイラが生成します。
Q:彼らが導入する前は不可能であった、どのような種類の問題を解決していますか?
A:これは、カスタムの追加、サブオペレーションの関数ではなく、演算子のオーバーロードのような構文糖のようなものですが、実際のロジックの1〜3行を一部のクラスにラップするなど、不要なコードの行をさらに節約します。一部のエンジニアは、行数が少ないとエラーが発生する可能性が低いと考えています(私もそう思います)
使用例
auto x = [=](int arg1){printf("%i", arg1); };
void(*f)(int) = x;
f(1);
x(1);
ラムダについての補足。質問には含まれません。興味がない場合は、このセクションを無視してください
1.キャプチャされた値。キャプチャできるもの
1.1。ラムダで静的ストレージ期間を持つ変数を参照できます。それらはすべて捕獲されます。
1.2。「値ごと」のキャプチャ値にラムダを使用できます。このような場合、キャプチャされた変数は関数オブジェクト(クロージャー)にコピーされます。
[captureVar1,captureVar2](int arg1){}
1.3。参考にキャプチャーできます。&-このコンテキストでは、ポインタではなく参照を意味します。
[&captureVar1,&captureVar2](int arg1){}
1.4。すべての非静的変数を値または参照によってキャプチャする表記法が存在します
[=](int arg1){} // capture all not-static vars by value
[&](int arg1){} // capture all not-static vars by reference
1.5。すべての非静的変数を値によって、または参照によってキャプチャし、smthを指定する表記法が存在します。もっと。例:すべての非静的変数を値でキャプチャしますが、参照でキャプチャしますParam2
[=,&Param2](int arg1){}
参照ではなくすべての静的でない変数をキャプチャしますが、値をキャプチャしますParam2
[&,Param2](int arg1){}
2.戻り型の控除
2.1。ラムダが1つの式である場合は、ラムダの戻り値の型を推定できます。または、明示的に指定することもできます。
[=](int arg1)->trailing_return_type{return trailing_return_type();}
lambdaに複数の式がある場合、戻り値の型は末尾の戻り値の型を介して指定する必要があります。また、同様の構文を自動関数とメンバー関数に適用できます
3.キャプチャされた値。キャプチャできないもの
3.1。キャプチャできるのはローカル変数のみで、オブジェクトのメンバー変数はキャプチャできません。
4.Сonversions
4.1 !! ラムダは関数ポインターではなく、無名関数でもありませんが、キャプチャーなしのラムダは暗黙的に関数ポインターに変換できます。
PS
ラムダ文法情報の詳細については、プログラミング言語の作業草案C ++#337、2012-01-16、5.1.2を参照してください。ラムダ式、p.88
C ++ 14では、「init capture」という名前の追加機能が追加されました。クロージャーデータメンバーの任意の宣言を実行できます。
auto toFloat = [](int value) { return float(value);};
auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);};
[&,=Param2](int arg1){}
は有効な構文ではないようです。正しい形式は次のようになります[&,Param2](int arg1){}
ラムダ関数は、インラインで作成する無名関数です。一部のユーザーが説明したように変数をキャプチャできます(例:http : //www.stroustrup.com/C++11FAQ.html#lambda)が、いくつかの制限があります。たとえば、このようなコールバックインターフェイスがある場合、
void apply(void (*f)(int)) {
f(10);
f(20);
f(30);
}
関数をその場で記述して、以下に適用するために渡された関数のように使用できます。
int col=0;
void output() {
apply([](int data) {
cout << data << ((++col % 10) ? ' ' : '\n');
});
}
しかし、これを行うことはできません:
void output(int n) {
int col=0;
apply([&col,n](int data) {
cout << data << ((++col % 10) ? ' ' : '\n');
});
}
C ++ 11標準の制限のため。キャプチャを使用したい場合は、ライブラリと
#include <functional>
(または間接的に取得するアルゴリズムのような他のSTLライブラリ)そして通常の関数を次のようにパラメータとして渡す代わりにstd :: functionを操作します:
#include <functional>
void apply(std::function<void(int)> f) {
f(10);
f(20);
f(30);
}
void output(int width) {
int col;
apply([width,&col](int data) {
cout << data << ((++col % width) ? ' ' : '\n');
});
}
apply
ファンクタを受け入れるテンプレートであれば、それは機能します
lambda expression
C ++ Bjarne Stroustrupの著者による本の***The C++ Programming Language***
第11章(ISBN-13:978-0321563842)で、最も優れた説明の1つが提供されています。
What is a lambda expression?
ラムダ式、時々も呼ばラムダ としての機能又は(厳密に言えば、誤ったが、口語) ラムダは、定義および使用するための簡略表記である匿名関数オブジェクト。operator()で名前付きクラスを定義し、後でそのクラスのオブジェクトを作成して、最後にそれを呼び出す代わりに、省略形を使用できます。
When would I use one?
これは、操作を引数としてアルゴリズムに渡したい場合に特に便利です。グラフィカルユーザーインターフェイス(およびその他の場所)のコンテキストでは、そのような操作はコールバックと呼ばれることがよくあります。
What class of problem do they solve that wasn't possible prior to their introduction?
ここで私はラムダ式で行われたすべてのアクションはそれらなしで解決できると思いますが、はるかに多くのコードとはるかに大きな複雑さがあります。ラムダ式これは、コードを最適化する方法であり、コードをより魅力的にする方法です。Stroustupによる悲しみ:
最適化の効果的な方法
Some examples
ラムダ式を介して
void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
for_each(begin(v),end(v),
[&os,m](int x) {
if (x%m==0) os << x << '\n';
});
}
または関数経由
class Modulo_print {
ostream& os; // members to hold the capture list int m;
public:
Modulo_print(ostream& s, int mm) :os(s), m(mm) {}
void operator()(int x) const
{
if (x%m==0) os << x << '\n';
}
};
あるいは
void print_modulo(const vector<int>& v, ostream& os, int m)
// output v[i] to os if v[i]%m==0
{
class Modulo_print {
ostream& os; // members to hold the capture list
int m;
public:
Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
void operator()(int x) const
{
if (x%m==0) os << x << '\n';
}
};
for_each(begin(v),end(v),Modulo_print{os,m});
}
あなたが必要な場合はlambda expression
、以下のような名前を付けることができます:
void print_modulo(const vector<int>& v, ostream& os, int m)
// output v[i] to os if v[i]%m==0
{
auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
for_each(begin(v),end(v),Modulo_print);
}
または別の簡単なサンプルを想定する
void TestFunctions::simpleLambda() {
bool sensitive = true;
std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});
sort(v.begin(),v.end(),
[sensitive](int x, int y) {
printf("\n%i\n", x < y);
return sensitive ? x < y : abs(x) < abs(y);
});
printf("sorted");
for_each(v.begin(), v.end(),
[](int x) {
printf("x - %i;", x);
}
);
}
次に生成されます
0
1
0
1
0
1
0
1
0
1
0ソート済みx-1; x-3; x-4; x-5; x-6; x-7; x-33;
[]
-これはキャプチャリストまたはlambda introducer
:lambdas
ローカル環境へのアクセスが必要ない場合は、使用できます。
本からの引用:
ラムダ式の最初の文字は常に[です。ラムダイントロデューサは、さまざまな形式をとることができます。
• []:空のキャプチャリスト。これは、周囲のコンテキストのローカル名をラムダ本体で使用できないことを意味します。このようなラムダ式の場合、データは引数または非ローカル変数から取得されます。
• [&]:参照により暗黙的にキャプチャします。すべてのローカル名を使用できます。すべてのローカル変数は参照によってアクセスされます。
• [=]:暗黙的に値でキャプチャします。すべてのローカル名を使用できます。すべての名前は、ラムダ式の呼び出し時に取得されたローカル変数のコピーを指します。
• [capture-list]: 明示的なキャプチャ。キャプチャリストは、参照または値によってキャプチャ(つまり、オブジェクトに格納)されるローカル変数の名前のリストです。&が前に付いた名前の変数は、参照によってキャプチャされます。他の変数は値によってキャプチャされます。キャプチャリストには、これと名前の後に...を要素として含めることもできます。
• [&、キャプチャリスト]:リストに記載されていない名前を持つすべてのローカル変数を暗黙的に参照してキャプチャします。キャプチャリストにはこれを含めることができます。リストされた名前の前に&を付けることはできません。キャプチャリストで指定された変数は、値でキャプチャされます。
• [=、キャプチャリスト]:リストに記載されていない名前を持つすべてのローカル変数を暗黙的に値でキャプチャします。キャプチャリストにこれを含めることはできません。リストされた名前の前には&を付ける必要があります。キャプチャリストで名前が付けられた変数は、参照によってキャプチャされます。
&が前に付いているローカル名は常に参照によってキャプチャされ、&が前に付いていないローカル名は常に値によってキャプチャされることに注意してください。参照によるキャプチャのみが、呼び出し環境での変数の変更を許可します。
Additional
Lambda expression
フォーマット
追加の参照:
for (int x : v) { if (x % m == 0) os << x << '\n';}
まあ、私が見つけた1つの実用的な使用法は、ボイラープレートコードの削減です。例えば:
void process_z_vec(vector<int>& vec)
{
auto print_2d = [](const vector<int>& board, int bsize)
{
for(int i = 0; i<bsize; i++)
{
for(int j=0; j<bsize; j++)
{
cout << board[bsize*i+j] << " ";
}
cout << "\n";
}
};
// Do sth with the vec.
print_2d(vec,x_size);
// Do sth else with the vec.
print_2d(vec,y_size);
//...
}
ラムダなしでは、さまざまなbsize
ケースで何かをする必要があるかもしれません。もちろん関数を作成することもできますが、soul user関数のスコープ内で使用を制限したい場合はどうでしょうか?ラムダの性質はこの要件を満たし、私はその場合に使用します。
C ++のラムダは、「すぐに使える関数」として扱われます。はい、文字通り外出先で、あなたはそれを定義します。これを使って; 親関数スコープが終了すると、ラムダ関数はなくなります。
c ++はc ++ 11でそれを導入し、誰もがあらゆる可能な場所で同じように使い始めました。例とラムダとは何かはここで見つけることができますhttps://en.cppreference.com/w/cpp/language/lambda
存在しないがすべてのC ++プログラマーが知っておくべき重要な点について説明します
ラムダはあらゆる場所で使用することを意図したものではなく、すべての関数をラムダで置き換えることはできません。また、通常の機能と比較して最速ではありません。ラムダで処理する必要があるオーバーヘッドがあるためです。
それは確かにいくつかのケースで行数を減らすのに役立ちます。基本的には、同じ関数で1回以上呼び出されるコードのセクションに使用できます。そのコードは、スタンドアロン関数を作成できるようにするために、他の場所には必要ありません。
以下はラムダの基本的な例とバックグラウンドで何が起こるかです。
ユーザーコード:
int main()
{
// Lambda & auto
int member=10;
auto endGame = [=](int a, int b){ return a+b+member;};
endGame(4,5);
return 0;
}
コンパイルがそれを拡張する方法:
int main()
{
int member = 10;
class __lambda_6_18
{
int member;
public:
inline /*constexpr */ int operator()(int a, int b) const
{
return a + b + member;
}
public: __lambda_6_18(int _member)
: member{_member}
{}
};
__lambda_6_18 endGame = __lambda_6_18{member};
endGame.operator()(4, 5);
return 0;
}
ご覧のとおり、使用するとどのようなオーバーヘッドが追加されますか。したがって、どこでも使用することはお勧めできません。該当する場所で使用できます。
解決する1つの問題:constメンバーを初期化するために出力パラメーター関数を使用するコンストラクターでの呼び出しのラムダよりも単純なコード
クラスのconstメンバーを初期化するには、その出力を出力パラメーターとして返すことで値を設定する関数を呼び出します。