LoginSignup
33
10

More than 3 years have passed since last update.

C++20標準ライブラリ std::ranges::subrange のひみつ

Last updated at Posted at 2019-06-05

C++2a(C++20)標準ライブラリ入りが予定されている「範囲(Ranges)ライブラリ」のうち、クラステンプレート宣言がやたら入り組んでいる std::ranges::subrange を(ちょっとだけ)分解してみるお話です。

本記事では2019年6月現在のC++仕様草案 N4810 を参照します。

2020年9月追記:C++2a仕様の最終ドラフト N4861 では、std::ranges::subrange クラステンプレートの意味論は変わっていませんが、同クラステンプレート定義で利用するコンセプト名は変更されています。

範囲ライブラリ

C++2aで新たに標準入りした範囲ライブラリによって、従来C++標準ライブラリで提供されていたイテレータ(Iterator)よりも便利で安全なコード記述が可能となります(なるはずです)。単純化した説明では、これまではイテレータのペアとして表現せざるを得なかった “範囲(Range)” を、単一オブジェクトで表現できるようになります。本記事ではこれ以上深入りしません。

c++2a範囲ライブラリの利用例
#include <iostream>
#include <vector>
#include <ranges>
namespace view = std::ranges::view;

auto ints = std::vector{0, 1, 2, 3, 4, 5};
auto even = [](int i){ return 0 == i % 2; };
auto square = [](int i) { return i * i; };
for (int i : ints | view::filter(even) | view::transform(square)) {
  std::cout << i << ' ';  // 0 4 16 を出力
}

本記事で取り上げる std::ranges::subrange クラステンプレート(以下subrange)は範囲ライブラリの一部として定義されます1。ただし通常のC++アプリケーションプログラマが直接利用するクラスというよりも、std::ranges::search関数などの探索系アルゴリズムが返す「部分範囲(subrange)」表現のためにライブラリ実装者が利用するクラスです。

2020年9月追記:std::ranges::subrange クラステンプレートは、C++17現在のイテレータ・ペアからC++2aの範囲(Range)へ変換するアダプタとしても利用できます。

subrangeクラステンプレート

C++2a標準ライブラリにおける subrange クラステンプレート宣言は下記コードの通りです。なんて分かりやすい!

C++2a(N4810)
namespace std::ranges {
  template<Iterator I, Sentinel<I> S = I, subrange_kind K =
      SizedSentinel<S, I> ? subrange_kind::sized : subrange_kind::unsized>
    requires (K == subrange_kind::sized || !SizedSentinel<S, I>)
  class subrange : public view_interface<subrange<I, S, K>> {
    /*...*/
  };
}

C++2a言語仕様で新たに導入される コンセプト(Concepts) に関する前提知識がないと全くの意味不明、むしろ知識があっても記号の羅列にしか見えないかもしれません。

subrangeクラステンプレート w/o コンセプト

ひとまずコンセプトに関する構文を無視して、C++17現在の言語機能のみで表現してみましょう。

C++17相当(requires節を削除)
namespace std::ranges {
  template<class D> class view_interface;
  enum class subrange_kind : bool { unsized, sized };

  template<class I, class S = I, subrange_kind K = /*...*/>
  class subrange : public view_interface<subrange<I, S, K>> {
    /*...*/
  };
}

だいぶスッキリしました。subrange クラステンプレートは3個のテンプレートパラメータI, S, Kをとり、2番目の型パラメータSと3番目の非型パラメータKにはデフォルト値が指定されています。

  • I = イテレータ型
  • S = 番兵型(通常はイテレータ型Iに等しい)
  • K = 部分範囲の長さが有界(sized)か否か(unsized)を表す定数(通常はsized

subrange は自身をテンプレート引数に指定した基底クラス view_interface<subrange<I, S, K>> から派生するCRTPイディオムを利用しています。view_interface<D> から継承したクラスは “Viewオブジェクト2”、つまり “軽量な範囲(Range)オブジェクト3” として扱えるようになります。本記事ではこれ以上深入りしません(2回目)。

subrangeクラステンプレート w/ コンセプト

あらためてC++2a標準ライブラリに立ち返りつつ、subrange クラステンプレートから参照される3つのコンセプト(Iterator, Sentinel, SizedSentinel)も引用しておきます。

C++2a(N4810)
namespace std {
  template<class I> concept Iterator = /*...*/;
  template<class S, class I> concept Sentinel = /*...*/;
  template<class S, class I> concept SizedSentinel = /*...*/;
}
namespace std::ranges {
  template<class D> class view_interface;
  enum class subrange_kind : bool { unsized, sized };

  template<Iterator I, Sentinel<I> S = I, subrange_kind K =
      SizedSentinel<S, I> ? subrange_kind::sized : subrange_kind::unsized>
    requires (K == subrange_kind::sized || !SizedSentinel<S, I>)
  class subrange : public view_interface<subrange<I, S, K>> {
    /*...*/
  };
}

テンプレートパラメータリスト部でのコンセプト指定(template<C T>)は、「テンプレートパラメータ型TがコンセプトC<T>を満たす」ことを表現しており、requires節を用いたtemplate<class T> requires C<T>と同義です。

つまり subrange クラステンプレートは、テンプレートパラメータが下記3つの制約(constraints)を満たすときに限ってインスタンス化されます。

  • 型テンプレートパラメータ IIterator<I> コンセプトを満たし、かつ
  • 型テンプレートパラメータ SSentinel<S, I> コンセプトを満たし、かつ
  • 非型テンプレートパラメータ K == sized もしくは SizedSentinel<S, I> コンセプトを満たさないとき。
    • 非型テンプレートパラメータ K のデフォルト値は、SizedSentinel<S, I> コンセプトを満たす場合は sized を、そうでなければ unsized をとる。

これで subrange クラステンプレート宣言を解釈できるようになりましたよね?本記事ではこれ以上深入りしません(3回目)


  1. [range.subrange]/p1: "The subrange class template combines together an iterator and a sentinel into a single object that models the View concept. Additionally, it models the SizedRange concept when the final template parameter is subrange_kind::sized." 

  2. [view.interface]/p1: "The class template view_interface is a helper for defining View-like types that offer a container-like interface." 

  3. [range.req.general]/p2: "The View concept specifies requirements on a Range type with constant-time copy and assign operations." 

33
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
10