yohhoyの日記

技術的メモをしていきたい日記

汎用ラムダキャプチャとunique_ptr型とmove関数の小さな罠

C++14で導入された汎用ラムダキャプチャ(generalized lambda capture)と、コピー不可かつムーブ可能オブジェクトの扱いについてメモ。

汎用ラムダキャプチャ構文でムーブキャプチャしたstd::unique_ptr型の変数up1を、そのラムダ式中でさらに変数up2へムーブしようとするとコンパイルエラーを引き起こす。

// C++14以降
#include <memory>

auto up0 = std::make_unique<int>(42);
auto lm = [up1 = std::move(up0)] {
  auto up2 = std::move(up1);  // NG: ill-formed
};

ラムダ本体は暗黙にクロージャ型(closure type)の const メンバ関数、つまり前掲例ではvoid ClosureType::operator()() const相当であり、ムーブキャプチャされた変数up1の型はconst std::unique_ptr<int>となる。よってラムダ本体のup2 = std::move(up1)ではコピーコンストラクタが選択されるが、std::unique_ptrクラスはコピー不可のためコンパイルエラーとなる。

ラムダ式宣言で mutable キーワードを使用すれば、期待通りの動作を実現できる。ただし、クロージャオブジェクトlmの1回目の呼び出しでup1はムーブ済みの状態となるため、2回以上呼び出すときはその振る舞いに留意すること。(→id:yohhoy:20120616

auto up0 = std::make_unique<int>(42);
auto lm = [up1 = std::move(up0)]() mutable {  // ★
  auto up2 = std::move(up1);  // OK: well-formed
};

lm();  // up1からup2へムーブ(その後 破棄)
// <lm中のup1>.get()==nullptr
lm();  // up1==up2==nullptr

ノート:ラムダ式宣言に mutable キーワードを指定する場合、パラメータ宣言部の括弧()が構文上必須となる。(→id:yohhoy:20120117

N3937 5.1.2/p5より一部引用。

The closure type for a non-generic lambda-expression has a public inline function call operator (13.5.4) whose parameters and return type are described by the lambda-expression's parameter-declaration-clause and trailing-return-type respectively. (snip) This function call operator or operator template is declared const (9.3.1) if and only if the lambda-expression's parameter-declaration-clause is not followed by mutable. (snip)

関連URL