C++20 でのその他の Lambda 機能

C++20 のラムダは、デフォルトで構築され、状態がない場合にコピー代入をサポートできます。ラムダは未評価のコンテキストで使用できます。さらに、this ポインターを暗黙的にコピーしたことを検出します。これは、ラムダによる未定義の動作の重大な原因がなくなったことを意味します。

列挙の最後の機能から始めたいと思います。 this ポインターを暗黙的にコピーすると、コンパイラーは未定義の動作を検出します。さて、未定義の動作とはどういう意味ですか? 未定義の動作では、プログラムの動作に制限がないため、何が起こるかは保証されません。

私は自分のセミナーでよく言います:未定義の動作がある場合、プログラムにはキャッチファイア セマンティックがあります。これは、コンピュータが発火する可能性さえあることを意味します。以前は、未定義の動作はより厳密に記述されていました。未定義の動作を使用すると、巡航ミサイルを発射できます。いずれにせよ、未定義の動作がある場合、残されたアクションは 1 つだけです:未定義の動作を修正することです。

次のセクションでは、意図的に未定義の動作を引き起こします。

this ポインターの暗黙的なコピー

次のプログラムは、コピーによって this ポインターを暗黙的にキャプチャします。

// lambdaCaptureThis.cpp

#include <iostream>
#include <string>

struct Lambda {
 auto foo() const {
 return [=] { std::cout << s << std::endl; }; // (1) 
 }
 std::string s = "lambda";
 ~Lambda() {
 std::cout << "Goodbye" << std::endl;
 }
};

auto makeLambda() { 
 Lambda lambda; // (2) 
 return lambda.foo();
} // (3)


int main() {
 
 std::cout << std::endl;

 auto lam = makeLambda(); 
 lam(); // (4) 
 
 std::cout << std::endl;
 
}

プログラムのコンパイルは期待どおりに機能しますが、これはプログラムの実行には当てはまりません。

プログラム lambdaCaptureThis.cpp で問題を見つけましたか?メンバー関数 foo (1) は、ラムダ [=] { std::cout <

この場合、C++20 コンパイラは警告を書き込む必要があります。 Compiler Explorer と GCC を使用した出力は次のとおりです。

C++20 に欠けている 2 つのラムダ機能は、それほどスリリングではないように思えます。C++20 のラムダは、デフォルトで構築され、状態がない場合にコピー代入をサポートできます。ラムダは未評価のコンテキストで使用できます。両方の機能を一緒に提示する前に、迂回路を作る必要があります:未評価のコンテキストとはどういう意味ですか?

未評価のコンテキスト

次のコード スニペットには、関数宣言と関数定義があります。

int add1(int, int); // declaration
int add2(int a, int b) { return a + b; } // definition
 

add1 は関数を宣言しますが、add2 はそれを定義します。これは、呼び出しなどの評価されたコンテキストで add1 を使用すると、リンク時にエラーが発生することを意味します。重要なことは、typeid や decltype などの未評価のコンテキストで add1 を使用できることです。どちらの演算子も未評価のオペランドを受け入れます。

// unevaluatedContext.cpp

#include <iostream>
#include <typeinfo> // typeid

int add1(int, int); // declaration
int add2(int a, int b) { return a + b; } // definition

int main() {

 std::cout << std::endl;

 std::cout << "typeid(add1).name(): " << typeid(add1).name() << std::endl; // (1)
 
 decltype(*add1) add = add2; // (2)
 
 std::cout << "add(2000, 20): " << add(2000, 20) << std::endl;
 
 std::cout << std::endl;
 
}

typeid(add1).name() (1) は型の文字列表現を返し、decltype (2) はその引数の型を推定します。

ステートレス ラムダは、デフォルト構築およびコピー割り当て可能

ラムダは未評価のコンテキストで使用できます

確かに、これはかなり長いタイトルです。ステートレス ラムダという用語は、初めて耳にするかもしれません。ステートレス ラムダは、その環境から何も取得しないラムダです。または、逆に言えば。ステートレス ラムダはラムダであり、ラムダ定義の最初の括弧 [] は空です。たとえば、ラムダ式 auto add =[ ](int a, int b) { return a + b; };ステートレスです。

機能を組み合わせると、非常に便利なラムダが得られます。

例を示す前に、いくつかの注意事項を追加する必要があります。標準テンプレート ライブラリ (std::map、std::multiset、および std::multimap) の他のすべての順序付き連想コンテナーなどの std::set は、デフォルトごとの std::less を使用してキーを並べ替えます。 std::less は、すべてのキーが辞書順に昇順で並べられることを保証します。 cppreference.com の std::set の宣言は、この順序付け動作を示しています。

template<
 class Key,
 class Compare = std::less<Key>,
 class Allocator = std::allocator<Key>
> class set;

では、次の例の順序で遊んでみましょう。

// lambdaUnevaluatedContext.cpp

#include <cmath>
#include <iostream>
#include <memory>
#include <set>
#include <string>

template <typename Cont>
void printContainer(const Cont& cont) {
 for (const auto& c: cont) std::cout << c << " ";
 std::cout << "\n";
}

int main() {
 
 std::cout << std::endl;

 std::set<std::string> set1 = {"scott", "Bjarne", "Herb", "Dave", "michael"};
 printContainer(set1);
 
 using SetDecreasing = std::set<std::string, decltype([](const auto& l, const auto& r){ return l > r; })>; // (1)
 SetDecreasing set2 = {"scott", "Bjarne", "Herb", "Dave", "michael"};
 printContainer(set2); // (2)

 using SetLength = std::set<std::string, decltype([](const auto& l, const auto& r){ return l.size() < r.size(); })>; // (1)
 SetLength set3 = {"scott", "Bjarne", "Herb", "Dave", "michael"};
 printContainer(set3); // (2)

 std::cout << std::endl;

 std::set<int> set4 = {-10, 5, 3, 100, 0, -25};
 printContainer(set4);

 using setAbsolute = std::set<int, decltype([](const auto& l, const auto& r){ return std::abs(l)< std::abs(r); })>; // (1)
 setAbsolute set5 = {-10, 5, 3, 100, 0, -25};
 printContainer(set5); // (2)
 
 std::cout << "\n\n";
 
}
 

set1 と set4 はキーを昇順に並べ替えます。 set2、set3、および set5 は、未評価のコンテキストでラムダを使用して一意に実行します。 using キーワード (1) は、次の行 (2) でセットを定義するために使用される型エイリアスを宣言します。セットを作成すると、ステートレス ラムダのデフォルト コンストラクターが呼び出されます。

Compiler Explorer と GCC のおかげで、これがプログラムの出力です。

プログラムの出力を調べると、驚くかもしれません。ラムダを使用する特別な set3 [](const auto&l, const auto&r){ return l.size()

次は?

C++20 のいくつかの小さな機能だけが残っています。小さな機能には新しい属性 [[likely]] と [[unlikely]] が含まれており、volatile のほとんどは廃止されました。