ラムダ式拡張
概要
C++11でラムダ関数の機能が追加された。C++11時点でのラムダ関数はキャプチャする変数を指定する事のみ可能であり、式をキャプチャ対象に指定したり、新たな変数を定義する事が出来なかった。
また引数の型も固定的でしか使用できないため、明示的に型名を指定するか、decltype(式)
による型指定をしなければならなかった。
そこで、C++14ではキャプチャの拡張、及びラムダ関数のジェネリック対応が行われた。
ジェネリック対応
ジェネリックに使用したい場合は、単純に引数の型をauto
で指定するだけで良い。
定義したラムダ関数に対して異なる型の引数を渡して利用可能である。
int main() {
auto f = [](auto v) { return v; };
f(0);
f('a');
}
上記のように、同じラムダ関数のオブジェクト(クロージャオブジェクトと呼ぶ)に対して異なる型を引数として指定可能である。 この時、ラムダ関数の実体は関数テンプレートで実現されており、以下のような関数オブジェクトと等価である。(実際にはもう少し複雑) 関数テンプレートであるため、パラメータパックを用いることも可能である。
struct closure {
template <class T>
auto operator() (T val) { return val; }
}
キャプチャ拡張
キャプチャに関する拡張は、キャプチャに対する初期化式が記述可能になった。 コピーキャプチャ、参照キャプチャの両方で初期化式を指定可能である。 以下のような構文となる。
[(&) 名前 = 初期化式, ...]
初期化式を含めたキャプチャをinit-capture
と呼ぶとすると、キャプチャ変数は以下のように定義されたものとして動作する。
auto init-capture;
つまり、新たな変数をキャプチャ変数として定義できるようになったことを意味する。
以下に参照キャプチャ、コピーキャプチャに初期化式を与えた場合のサンプルを示す。
int main() {
int x = 4;
auto y = [&r = x, x = x+1]() -> int {
r += 2;
return x+2;
}();
std::cout << x << std::endl; // 6
std::cout << y << std::endl; // 7
return 0;
}
この機能はムーブキャプチャとして利用する事が可能である。ムーブキャプチャ自体はラムダ関数の構文上許されていないが、初期化式でムーブを利用する事で実現出来る。
#include <memory>
auto bad_func() {
std::unique_ptr<int> p = std::make_unique<int>(10);
return [&p]() -> int { return *p; };
//return [p]() -> int { return *p; }; //error unique_ptr can't copy.
}
auto func() {
std::unique_ptr<int> p = std::make_unique<int>(10);
return [ptr = std::move(p)]() -> int { return *ptr; };
}
int main() {
auto bf = bad_func();
bf(); //error pは既に解放ずみ
auto f = func();
f(); //OK
}