Skip to content

Commit a199a49

Browse files
authored
Update lifetime_extension_in_range_based_for_loop.md #1246
1 parent d90163c commit a199a49

File tree

1 file changed

+41
-33
lines changed

1 file changed

+41
-33
lines changed

lang/cpp23/lifetime_extension_in_range_based_for_loop.md

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ for ( init-statement(opt) for-range-declaration : for-range-initializer ) statem
1414

1515
ただし、次の場合には適用されない。
1616

17-
- 一時オブジェクトが関数の引数の場合
17+
- 一時オブジェクトが関数の引数として生成された場合
1818
- 一時オブジェクトの(この規定が適用されない場合の)寿命が `for-range-initializer` 完全式の終わりではない場合
1919

2020
##
@@ -45,39 +45,20 @@ U
4545
B
4646
```
4747

48-
以下の例では、`getstr()` が返す一時オブジェクトは関数の引数だから、延命されない
48+
範囲for文の危険性を減らすだけではなく、この仕様はRAIIのためのオブジェクトを無名で作るのに使うことができる
4949

50-
```cpp example
51-
import std;
52-
53-
std::vector<std::string> getstr() {
54-
return {"hello", "UB"};
55-
}
56-
57-
std::string wrap(const std::string& x) {
58-
return "[" + x + "]";
59-
}
60-
61-
int main()
62-
{
63-
for(auto&& c : wrap(getstr()[0])) {
64-
std::println("{}", c);
65-
}
66-
}
67-
```
68-
69-
### 出力
7050
```
71-
[
72-
h
73-
e
74-
l
75-
l
76-
o
77-
]
51+
// P2718R0より引用
52+
void f() {
53+
std::vector<int> v = { 42, 17, 13 };
54+
std::mutex m;
55+
for (int x : static_cast<void>(std::lock_guard<std::mutex>(m)), v) {
56+
...
57+
}
58+
}
7859
```
79-
80-
60+
ここでは、カンマ演算子を活用して実際にイテレートする範囲とは別にロックを獲得している。
61+
この一時オブジェクトは `for-range-initializer` の中で生じているから、範囲for文の終わりまでロックを維持できる。
8162

8263
## この機能が必要になった背景・経緯
8364

@@ -133,6 +114,32 @@ int main()
133114

134115
C++23では、`getstr()` の呼び出しが `for-range-initializer` の中にあるため、返った一時オブジェクトは参照 `r` と同じ寿命になる。したがってダングリング参照は発生せず、このコードは安全である。
135116

117+
### 例外規定について
118+
119+
"一時オブジェクトが関数の引数として生成された場合" とは、次のサンプルコードにおける `f2(T t)` の実引数 `t` として、呼び出される関数のスコープで生成されるような場合である。
120+
121+
```cpp
122+
// P2718R0より引用
123+
using T = std::list<int>;
124+
const T& f1(const T& t) { return t; }
125+
const T& f2(T t) { return t; }
126+
T g();
127+
128+
void foo() {
129+
for (auto e : f1(g())) {} // OK: g()の戻り値は延命される
130+
for (auto e : f2(g())) {} // 未定義動作
131+
}
132+
```
133+
134+
このような `t` は呼び出される関数から戻ると破棄されるから、その参照を返すことは未定義動作である。
135+
ここで未定義動作になることは範囲for文の危険性と無関係なので、寿命を延長するという解釈ができないようにこの例外規定が入った。
136+
137+
議論:
138+
- この `t` は "一時オブジェクトの寿命が `for-range-initializer` 完全式の終わりではない場合" にも該当すると考えられる
139+
- この `t` は、構文的に見ると `for-range-initializer` の中で生じたとは言えないという意見もある
140+
- "`for-range-initializer` の中" を実行時のことだと解釈すると、そこから呼び出された関数の中なども含むことになるが、それを排除する規定が "一時オブジェクトの寿命が `for-range-initializer` 完全式の終わりではない場合" ではないか
141+
- インライン展開されたときなどに効いてくるのかもしれない
142+
136143
## 検討されたほかの選択肢
137144
138145
一時オブジェクトの寿命について、範囲for文に限定しない汎用的な方法も検討されたが、最終的には範囲for文の例外規定となった。
@@ -141,7 +148,8 @@ C++23では、`getstr()` の呼び出しが `for-range-initializer` の中にあ
141148
142149
- [範囲for文](/lang/cpp11/range_based_for.md)
143150
144-
145151
## 参照
146-
152+
- [P2718R0 Wording for P2644R1 Fix for Range-based for Loop](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2718r0.html)
147153
- [地に足のついた範囲for文 - 地面を見下ろす少年の足蹴にされる私](https://onihusube.hatenablog.com/entry/2022/12/05/000923)
154+
- [範囲for文範囲初期化子内の一時オブジェクト延命の説明見直し
155+
#1246](https://github.com/cpprefjp/site/issues/1246)

0 commit comments

Comments
 (0)