コンテンツにスキップ

最終更新:2026-10-12

C++20 Ranges ライブラリ

ranges は C++20 の主要な機能の1つで、その中でも"view"が重要な部分です。C++20 より前は、標準ライブラリのアルゴリズムはイテレータを基に実装されていました。例えば:std::sort。

std::sort(v.begin() + 2, v.end())

イテレータ + アルゴリズムで複雑な操作を行えます。例えば:逆順にソートしたい場合:

std::sort(v.rbegin(), v.rend())

しかしこれにはいくつかの問題が伴います:

  • 互換性のない2つのイテレータを混在させやすい。
  • アルゴリズムの組み合わせ能力が弱く、中間変数を保存する必要がある。

例えば:学生情報システムがあり、**年齢が21〜25で GPA >= 3.5 の学生の GPA の合計**を求めたいとします。C++17 以前では、次のような擬似コードを書けます:

std::vector<Student> students
std::vector<Student> selected;
std::copy_if(students.begin(), students.end(), std::back_inserter(selected),
             [](const Student& student) {
               return student.age >= 21 && student.age <= 25 && student.gpa >= 3.5;
             });

double s = std::accumulate(
    selected.begin(), selected.end(), 0.0,
    [](double sum, const Student& student) { return sum + student.gpa; });

条件を満たす学生情報をフィルタリングする際、selected にコピーしてから合計を求める必要があることがわかります。

C++20 はより簡潔で効率的な書き方を導入しました。範囲(range)とパイプライン演算子 | で複数の操作をつなぐことで、中間変数なしで直接合計を求められます。例えば:

double s = 0.0;
for (const auto& student :
     students | std::views::filter([](const auto& s) {
       return s.age >= 21 && s.age <= 25 && s.gpa >= 3.5;
     }) | std::views::transform([](const auto& s) { return s.gpa; })) {
  s += student;
}

複数のアルゴリズムがシームレスにつながり、中間一時変数のメモリ割り当てが減り、可読性も大幅に向上します。パイプライン|で異なる操作を1つにつなげます。

それでは、C++20 ranges に関連する内容を一緒に見ていきましょう。

1.基礎概念

1.range

range は、シーケンスを表す抽象概念です。イテレータを持つ任意のコンテナ、または begin() と end() 関数を定義したオブジェクトであり得ます。std::vector、std::list などは範囲の例です。配列も範囲とみなせます。

2.view

view は Range への読み取り専用アクセスです。遅延評価の一種で、必要になったときにのみ計算されるため、実際にはデータを保存しません。例えば:std::views::filter と std::views::transform は view の典型的な例です。これらを使うと、新しいコンテナを実際に作成することなく range のフィルタリングと変換ができます。

3.algorithm

アルゴリズムは range や view に対して操作を行う関数です。例えば:std::sort、std::find などはアルゴリズムの例です。

4.|

パイプライン演算子|は、ビューとアルゴリズムをつなぎ、左側の結果を右側の入力にします。これによりコードがより明快で簡潔になります。例えば:students | std::views::filter(...) は students 範囲を std::views::filter に渡してフィルタリング操作を行い、その結果を後続の操作に渡します。

上の student の計算を例にとると、この例では範囲 students を|でビュー filter の入力とし、その結果をビュー transform の入力とし、最後に範囲を返します。この範囲をループし、累積アルゴリズムで合計を求めて結果を得ます。

double s = 0.0;
for (const auto& student :
     students | std::views::filter([](const auto& s) {
       return s.age >= 21 && s.age <= 25 && s.gpa >= 3.5;
     }) | std::views::transform([](const auto& s) { return s.gpa; })) {
  s += student;
}

range を使うと次のような利点があります:

  • コードの可読性の向上
  • 遅延評価:Ranges はビュー(view)の概念を導入し、遅延評価、つまり必要になったときにのみ結果を計算することを可能にします。

例えば:*v.begin() したときに初めて計算が行われます。

auto v = std::views::reverse(vec);
std::cout << *v.begin() << std::endl;
  • エラーの削少:Ranges の設計は、従来のイテレータ操作で起こりやすいエラー(互換性のないイテレータの使用など)を減らす助けになります。
  • など。

範囲の概念は、異なるタイプの範囲を記述するためにさまざまなコンセプトを導入しています。これらのコンセプトは、ジェネリックプログラミングにおいて範囲の特性をよりよく理解し制限するのに役立ちます。よく使われる範囲のコンセプトを以下に示します:

https://en.cppreference.com/w/cpp/ranges

概念 説明 コンテナの例
std::ranges::input_range 先頭から末尾まで少なくとも1回反復できる std::forward_list、std::list、std::duque、std::array、std::vector
std::ranges::forward_range 先頭から末尾まで複数回反復できる std::forward_list、std::list、std::duque、std::array、std::vector
std::ranges::bidirectional_range イテレータを後方にも--移動できる std::list、std::duque、std::array、std::vector
std::ranges::random_access_range 要素へ定数時間でジャンプできる [] std::duque、std::array、std::vector
std::ranges::contiguous_range 要素が常にメモリ上に連続して格納される std::array、std::vector

2.使用

この機能の使用は比較的簡単で、ヘッダファイルをインクルードしてインターフェースを使うだけです。

例えば:数値の集まりから偶数をフィルタリングします。

#include <ranges>
auto evenNumbers = numbers | std::views::filter([](int x) { return x % 2 == 0; });

コンパイル:-std を指定するだけです。

g++ -std=c++20 main.cc -o main

コンパイラのサポート状況は以下のリストをご覧ください:

https://en.cppreference.com/w/cpp/compiler_support/20

gcc >=10、clang >=13(部分サポート)、>=15 で完全サポートです。

関連トピック

コメント