範囲for文の歴史

この記事では、C++11で導入された後も改良が続いている**範囲for文**(range-based for statement)の発展の歴史を解説します。

# 範囲for文とは

範囲for文はRangeの要素を1つずつ取り出しながらループする繰り返し文です。

C++23における範囲for文の構文は以下の通りです。ただし、opt はその要素が省略できることを表します。

> for ( _init-statement_<sub>opt</sub> _for-range-declaration_ : _for-range-initializer_ ) _statement_

これは、以下の通常のfor文を使ったコードと概ね等しくなります。

> {
>    _init-statement_<sub>opt</sub>
>
>    auto&& range = _for-range-initializer_ ;
>    auto begin = _begin-expr_ ;
>    auto end = _end-expr_ ;
>    for ( ; begin != end; ++begin ) {
>       _for-range-declaration_ = *begin ;
>       statement
>    }
> }

ここで、

* _init-statement_ (初期化文): 変数宣言、エイリアス宣言、式文
* _for-range-declaration_ (for範囲宣言): Rangeの要素を受け取る変数の宣言もしくは構造化束縛
* _for-range-initializer_ (for範囲初期化子): Rangeを表す式または初期化子リスト

です。また、式 _begin-expr_ と _end-expr_ は次のように定まります。

* _for-range-initializer_ が配列型の式であれば、_begin-expr_ は `range`, _end-expr_ は `range + N`。ただし、Nは配列の要素数で、要素数が不明の場合はエラー
* _for-range-initializer_ がクラス型Cの式で、Cのスコープにおいて名前`begin`と`end`が共に見つかれば、_begin-expr_ は `range.begin()`, _end-expr_ は `range.end()`
* それ以外の場合、_begin-expr_ は `begin(range)`, _end-expr_ は `end(range)` となり、これらの呼び出しが何を呼び出すかは引数依存の名前探索(ADL)によって決まる

例:
```cpp
import std;

int main() {
    // {1, 2, 3, 4, 5} の要素を1つずつ取り出し、それぞれxに代入して内部の処理を実行する
    for(auto x : {1, 2, 3, 4, 5}) {
        std::print("{}", x); // => 12345
    }
}
```

なお、_for-range-initializer_ にて生じた一時オブジェクトは、関数の引数を除いて、範囲for文の終了まで寿命が延長されます。

:::note info
ところで、_begin-expr_ と _end-expr_ の決め方については `std::(ranges::)begin/end` が配列やクラスのメンバーの場合にも対応しているので、最後の規定だけあれば足りる気がしませんか？
それなのにわざわざ範囲for文の定義でカバーしているのは、おそらく `std::(ranges::)begin/end` を経由するオーバーヘッドを(実際にはインライン展開される可能性が高いとしても)避けるためではないかと思われます。
:::

:::note info
メンバー `begin`, `end` は、関数呼び出し演算子を適用できれば関数でなくても構いません。
:::

# C++11

C++11では範囲for文が初めて導入されました。

当時の範囲for文は以下の構文でした。

> for ( _for-range-declaration_ : _for-range-initializer_ ) _statement_

これは、以下のコードと等しくなります。

> {
>    auto&& range = _for-range-initializer_ ;
>    for ( auto begin = _begin-expr_, end = _end-expr_ ; begin != end; ++begin ) {
>       _for-range-declaration_ = *begin ;
>       statement
>    }
> }

`begin`, `end` は一度に定義されるため、同じ型に限られていました。

また、一時オブジェクトの寿命に関する特別ルールも存在せず、範囲の寿命が尽きないように注意が必要でした。

```cpp
#include <vector>
#include <string>
#include <iostream>

std::vector<std::string> getstr() {
  return {"hello", "UB"};
}

int main()
{
  for(auto&& c : getstr()[0]) { // ダングリング参照が発生
    std::cout << c << std::endl; // ダングリング参照にアクセスし未定義動作
  }
}
```

C++11で導入された初期化子リストは初めから _for-range-initializer_ で利用可能でした。

# C++14

C++14では範囲for文の仕様は変わっていません。

# C++17

C++17では、イテレーターの型と異なる型の終端イテレーター(番兵ともいう)を許容するようになりました。

* C++20のレンジライブラリでは、範囲for文における取り扱いだけでなく、一般にイテレーターと番兵の型は異なるものとして再定義されています

また、C++17で導入された構造化束縛が _for-range-declaration_ において使えるようになっています。

# C++20

C++20では、 _for-range-initializer_ がクラス型Cの式で、Cのスコープにおいて名前`begin`と`end`が**共に**見つかる場合に限り、_begin-expr_ は `range.begin()`, _end-expr_ は `range.end()` となるように変更されました。

それまではどちらか一方だけ見つかればOKでしたが、`begin` / `end` というメンバーをすでに持っていて、しかもそれが範囲for文で使われることを意図していないクラスに対して範囲for文を使えない原因になっていました。

* この変更はC++11に対する修正となっているため、使用する規格のバージョンをC++11にしていても適用されます

また、範囲for文でも初期化文を書けるようになりました。具体的には _init-statement_<sub>opt</sub> の部分がこのとき追加されています。

* C++17でif文/switch文に初期化文を書けるようになり、それとの一貫性を持たせるため。また、C++20時点では、一時オブジェクトの寿命を延ばすテクニックとしても有効でした

# C++23

C++23では、_for-range-initializer_ にて生じた一時オブジェクトは、範囲for文の終了まで寿命が延長されることが定められ、範囲for文はより安全に使えるようになりました。

これを通常のfor文に展開した後のコードでどう実現するかは規定されていませんが、例えば以下のように変数を追加すれば可能です。

```cpp
int main()
{
  // for(auto&& c : getstr()[0]) { // ダングリング参照が発生
  //  std::cout << c << std::endl; // ダングリング参照にアクセスし未定義動作
  // }
  {
    auto&& __expr0 = getstr();
    auto&& __range = expr0[0];
    auto __begin = __range.begin();
    auto __end = __range.end();
    for ( ; __begin != __end; ++__begin ) {
      auto&& c = *__begin;
      std::cout << c << std::endl;
    }
  }
}
```

また、初期化文でエイリアス宣言ができるようになりました(元々ここで`typedef`は書けたので一貫性のため)。

```cpp
for (using T = int; T e : v) {}
```

* `if` や `switch` の初期化文でも可能です

# 参考リンク

* [範囲for文](https://cpprefjp.github.io/lang/cpp11/range_based_for.html)
* [範囲forループの制限緩和](https://cpprefjp.github.io/lang/cpp17/generalizing_the_range-based_for_loop.html)
* [範囲for文がカスタマイゼーションポイントを見つけるルールを緩和](https://cpprefjp.github.io/lang/cpp20/relaxing_the_range_for_loop_customization_point_finding_rules.html)
* [範囲for文が範囲初期化子内で生じた一時オブジェクトを延命することを規定
](https://cpprefjp.github.io/lang/cpp23/lifetime_extension_in_range_based_for_loop.html)
* [構造化束縛](https://cpprefjp.github.io/lang/cpp17/structured_bindings.html)