Chapter 10 Outlook: Introduction of C++20

Chapter 10 Outlook: Introduction of C++20 に関するノート。C++20 が導入する重要な機能のいくつかを紹介するとのことだが、まだ脱稿していないようだ。

Concept

テンプレートプログラミングをさらに強化したものがコンセプトだ。テンプレートプログラミングはコンパイラーにテンプレート引数を評価させることが本質的だが、テンプレーしばしばさまざまな凶悪なエラーに遭遇する。これは、これまでのところ、テンプレート引数を検証したり制限したりすることができなかったから起こるのだ。次のようなコードでさえ読みにくいエラーメッセージを大量に出力する:

#include <list>
#include <algorithm>

int main() {
    std::list<int> l = {1, 2, 3};
    std::sort(l.begin(), l.end());
    return 0;
}

このコードエラーの根本的な原因は、std::sort が受け付けるのはランダム反復子のペアである必要があり、std::list から得られる反復子はランダムではないからだ。コンセプトの言葉で言えば、std::list の反復子は std::sort のランダム反復子というコンセプトの制約を満たさない.コンセプトを導入した後、次のようにテンプレート引数を制約することができる:

template <typename T>
requires Sortable<T> // Sortable is a concept
void sort(T& c);

これをこう略す:

template<Sortable T> // T is a Sortable typename
void sort(T& c);

型として直接使うこともある:

void sort(Sortable& c); // c is a Sortable type object

読者ノート

ここまでは本書をそのまま書き写しただけだ。

template<typename T>
concept Sortable = requires (T a)
{
    // requirements...
};

このような Sortable が存在する事前条件を仮定してから上述の sort の宣言が許される。ただし、引数リストに Sortable を書く最後の例は手許のコンパイラーではエラーになる。

読者ノート

  1. 要件

  2. 概念

  3. 制約

要件 (requirement) は表現が二レベルあると考えられる。一つは文章の形式で述べられるものだ。例えば名前付き要件 CopyConstructible が、ある型 T が lvalue 式から T オブジェクトをコピー構築可能であることを指定するものだというようなことだ。

もう一つは C++ のコードの形式で表現したものだ。例えば、ヘッダーファイル <concepts> で定義されているテンプレート概念 std::copy_constructible<T> は上述の CopyConstructible を表現する C++ 標準ライブラリーコンポーネントだ。このように、要件には C++20 で概念言語機能を用いて定式化されているものがある。

クラステンプレート、関数テンプレート、非テンプレート関数(典型的にはクラステンプレートのメンバー)は、テンプレート引数に対する要件を指定する制約と関連付けられることがあり、この制約を、最も適切な関数オーバーロードおよびテンプレート特殊化を選択するために使用することができる。このような名前付き要件からなる集合を概念 (concept) と呼ぶ(集合一つが概念一つを表す)。

各概念は述語であり、コンパイル時に bool 値に評価され、制約として使用されるテンプレートのインターフェイスの一部となる。本文の Sortable は概念であり関数テンプレート sort のテンプレート引数に対する制約でもあるということだ。

制約 (constraint) とは、テンプレート引数に対する要件を指定する論理演算とオペランドの連なりだ。これらは requires 式の中に現れるか、概念の本体として直接現れることがあり得る。

詳しくは: Constraints and concepts (since C++20) - cppreference.com

Module

読者ノート

Modules (since C++20) によると、C++20 からキーワード importexport を中心とするモジュール機能を実現しているようだ。

C++ で共通機能を実装するときは、その宣言と定義をヘッダーファイルとソースファイルそれぞれに分割してコードを書くものだが、これがソースファイルのみで済ませられるようになると読める。

手許の g++ だと、コンパイルオプションをどう指定しても説明コードをコンパイルできない。

Contract

読者ノート

Contracts は C++20 においてはやらないことになったもよう。少ししか調べていないので機能の概略は不明。

Range

読者ノート

C++20 に新規ヘッダーファイル <ranges> で追加された一連の機能を見ていく。このライブラリーは <algorithm><iterator> を拡張・一般化し、合成しやすくエラーの少ないライブラリーにすることで、より強力にしたものとのことだ。

このライブラリーは、反復可能な列(範囲)を間接的に表現する軽量オブジェクトである範囲ビューを作成、操作する。ビューは対象範囲を間接的に表現するものだ。

ここでは範囲とは次の四つの抽象概念とする:

  • [begin, end) 反復子ペア

  • [start, size) 範囲の始点と長さのペア

  • [start, predicate) 範囲の視点と述語のペア

  • [start..) 範囲の下限のみ(上に有界でない範囲)

名前空間 std::ranges があり、そこには範囲に対するアルゴリズムの集合がある。一例を挙げると:

  • empty は範囲が空か否かを返す。

  • {,c}data は連続範囲の生データへの参照を返す。

  • {,c}{,r}{begin,end} は範囲の両端に対応する反復子を返す。

  • {,s}size は範囲の長さを返す。ssigned の意。

名前空間 std::views = std::ranges::views には範囲適合器と呼ばれる機能群が存在する。例として配列を views::drop にパイプして先頭要素二個を捨てるコードを挙げる:

const auto nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// 3 4 5 6 7 8 9
for (int i : nums | std::views::drop(2))
    std::cout << i << ' ';

適合器に渡す view を生成するには、できあいの生成関数群が有用かもしれない。C++20 には views::iota しか意味のあるものはないが、C++23 から views::repeat, views::cartesian_product などがある。たぶん次のコードがコンパイルできる(手許のコンパイラーではエラー):

const auto cq = std::array{"", "c"};
const auto rq = std::array{"", "r"};
const auto ends = std::array{"begin", "end"};

 for (const auto& [c, r, e] : std::views::cartesian_product(cq, rq, ends))
     std::cout << c << r << e << std::endl;

細部についてはまた今度。

Coroutine

読者ノート

コルーチンは関数の特殊な形態であって、実行を中断、再開することができるものだ。関数ではあるが、中断と再開を実現するためにスタック領域の扱いが特別だ。非同期で実行する逐次処理コードが可能になり、また、遅延計算された無限列に関するアルゴリズムなどにも応用可能だ。

関数がコルーチンであるには、その定義に次のいずれかが含まれていることが必要だ:

  • co_wait

  • co_yield

  • co_return

コルーチンを定義するには普通の関数以上の制約がある。可変長変数や auto 戻り型などは使えないし、コンストラクター、デストラクターをコルーチンにすることはできない。

コルーチン周りはとても要約できるような言語仕様ではないので、このへんにしておく。 Python や JavaScript に相当する機能があるので、そちらも併せて復習するのがいいだろう。リファレンスのサンプルコードをコンパイルして動作確認して終えよう。

Coroutines (C++20) - cppreference.com

次のコードは手許の g++ でコンパイル可能(しかも遅くない):

  • resuming_on_new_thread, switch_to_new_thread

  • fibonacci_sequence

コードを大量に用意しないと使い物にならないライブラリーであるかもしれない。

Conclusion

C++17 以前の現代 C++ を習得してから C++20 以降に着手するのが自然だろう。まだ早い。

Further Readings

C++ compiler support - cppreference.com

今使っている g++ は 11.3.0 だ。これは C++20 の新機能、ライブラリーをほとんどすべて網羅していると読める。

History of C++ - cppreference.com

当然だが C++20 は 2020 年。