@@ -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## 例
4545B
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
134115C++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