Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion lang/cpp20/simplifying_implicit_lambda_capture.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,60 @@ void f() {
}
```

構造化束縛で導入された名前はラムダ式でキャプチャできない、と明記された。しかしその後、「[構造化束縛を拡張して通常の変数宣言のように使用できるようにする](extending_structured_bindings_to_be_more_like_variable_declarations.md)」の仕様でそれが可能となったため、この仕様変更は打ち消された。
本提案では一旦、構造化束縛で導入された名前はラムダ式でキャプチャできない、と明記された。その後、「[構造化束縛を拡張して通常の変数宣言のように使用できるようにする](extending_structured_bindings_to_be_more_like_variable_declarations.md)」の仕様でそれが可能となったため、この部分は打ち消された。

## 仕様
ラムダ式がどのローカル変数をキャプチャできるか、またそれをどこで使用できるかを規定するために、odr-usableという用語が導入された。

ローカル変数、関数のパラメータ、構造化束縛で導入された名前、`*this`のように、関数のローカルな文脈に属するものをローカルエンティティ (*local entity*) と呼ぶ。ローカルエンティティは、その宣言から使用する地点までの間に挟まるスコープが、すべて以下のいずれかである場合に、その地点でodr-usableとなる。

- ブロックスコープ (`{}`で囲まれた文のスコープ)
- ラムダ式の関数パラメータスコープ (ラムダ式のパラメータを宣言できる範囲) であり、そのラムダ式がそのローカルエンティティを明示的にキャプチャしているかデフォルトキャプチャをもっていて、かつそのラムダ式の本体のブロックスコープも、使用する地点までの間に挟まっている

odr-usableではないローカルエンティティをodr-usedした場合、そのプログラムは不適格となる。

```cpp
void f(int n) {
[] { n = 1; }; // エラー: nをキャプチャしていないため、odr-usableではない
[&] { n = 1; }; // OK: nをキャプチャしており、ラムダ式の本体の中で使用している

struct A {
void g() { n = 2; } // エラー: ローカルクラスのスコープと、そのメンバ関数の
}; // 関数パラメータスコープが挟まる

void g(int = n); // エラー: ラムダ式ではない関数の関数パラメータスコープが挟まる

[=](int k = n) {}; // エラー: ラムダ式の関数パラメータスコープは挟まるが、
// 本体のブロックスコープは挟まらない

[&] { [n]{ return n; }; }; // OK: 内側のラムダ式は、外側のラムダ式の本体の中にある
}
```

### ラムダ式のデフォルト引数で外側のローカル変数を使用できない理由
キャプチャが効くのはラムダ式の本体の中だけであり、パラメータの宣言はその外側にある。そのため`[=](int k = n) {}`の`n`は、キャプチャしたコピーではなく、外側の`n`そのものを指す。

デフォルト引数が評価されるのは、ラムダ式を書いた場所ではなく、クロージャオブジェクトを呼び出した場所である。クロージャオブジェクトはそれを作った関数の外へ持ち出せるため、仮にこれを許すと、すでに生存期間の終わったローカル変数を読むことになりうる。

```cpp
auto f(int n) {
return [=](int k = n) { return k; }; // エラー
}

int main() {
auto g = f(3);
g(); // 仮に適格だとすると、ここで関数f()のパラメータnを読むことになる
}
```


## <a id="relative-page" href="#relative-page">関連項目</a>
- [C++17 構造化束縛](/lang/cpp17/structured_bindings.md)
- [C++20 構造化束縛を拡張して通常の変数宣言のように使用できるようにする](extending_structured_bindings_to_be_more_like_variable_declarations.md)
- [C++20 構造化束縛した変数の参照キャプチャを許可](reference_capture_of_structured_bindings.md)

## 参照
- [P0588R1 Simplifying implicit lambda capture](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0588r1.html)
- C++20で、ラムダ式での暗黙のキャプチャの規定が整理され、odr-usableが導入された
- [CWG Issue 2380. capture-default makes too many references odr-usable](https://cplusplus.github.io/CWG/issues/2380.html)
- C++20の策定中に、odr-usableの条件へ「ラムダ式の本体のブロックスコープも挟まっていること」が追加された。この条件がないと、デフォルトキャプチャを書くだけでラムダ式のパラメータのデフォルト引数から外側のローカル変数を使用できてしまっていた
Loading