391.2K Views
August 21, 24
スライド概要
【CEDEC ページ】
https://cedec.cesa.or.jp/2024/session/detail/s6609118bb9dfd/
【前回 2020 のスライド】
https://speakerdeck.com/cpp/cedec2020
C++
ゲーム開発者のための C++17 ~ C++23, 近年の C++ 規格策定の動向 鈴木 遼 松村 哲郎 安藤 弘晃 cpprefjp / Siv3D cpprefjp cpprefjp
C++ に強くなる 60 分! ◆ 前半: ゲーム開発に役立つ C++17 ~ C++23 の機能 • C++17 / C++20 / C++23 でより使いやすくなった C++, ゲームやツール開発に役立つ機能をピックアップして 35 個のガイドラインに。 • 講演を聞くことで、モダンな知識とセンスで C++ を書けるように。 ◆ 後半: 近年の C++ 規格策定の動向 • C++ 規格マニアのためのトピックを 8 つ紹介。 • 講演を聞くことで、C++ 標準化の流れを追えるようになり、 将来の C++ の進化を見据えたソフトウェア・API 設計ができるように。 2
最新の C++ を解説する オープンソースの日本語 Web サイトを作っています cpprefjp cpprefjp.github.io • 標準ライブラリや言語機能のリファレンスとサンプル • 各規格における新機能のリストアップ cppmap cppmap.github.io • C++ の書籍やイベント、開発ツールなどの情報 3
近年の C++ の進化の流れ 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 C++11 C++14 C++17 C++20 C++23 標準化後の C++ の初メジャーアップデート。モダン C++ の基礎を築く • 範囲 • • スマートポインタ • • ラムダ式 • ラムダ式 • • ムーブセマンティクス 4
近年の C++ の進化の流れ 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 C++11 C++14 C++17 C++20 C++23 C++11 の改良版。既存機能を改良するマイナーアップデートが中心 • ジェネリックラムダ • 戻り値型推論 • 変数テンプレート • 桁区切り文字 5
近年の C++ の進化の流れ 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 C++11 C++14 C++17 C++20 C++23 コードを簡潔にする文法や標準ライブラリの拡充 • 構造化束縛 • • • インライン変数 6
近年の C++ の進化の流れ 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 C++11 C++14 C++17 C++20 C++23 モダンなプログラミング手法を取り入れた大規模アップデート • コンセプト • ビット操作 • 三方比較演算子 • 指示付き初期化 • Ranges • • モジュール • コルーチン 7
近年の C++ の進化の流れ 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 C++11 C++14 C++17 C++20 C++23 C++20 の新機能をさらに発展させる補完的なアップデート • • • 添字演算子の多次元サポート • 標準ライブラリモジュール 8
過去(CEDEC 2020)講演との関係 前回の講演 C++11 C++14 C++17 C++20 C++23 9
過去(CEDEC 2020)講演との関係 今回の講演 C++11 C++14 C++17 C++20 C++23 10
仕事場に最新の C++ が来ていなくても・・・ • 新機能は、従来の文法やライブラリ機能の欠点・間違いやすさを改善。 現在使っている C++ の限界や罠に気付くことができる。 • 新しいライブラリ機能を試せる先行実装がある。 現在のバージョンで活用したり、将来に備えた設計の準備ができる。 • 新しいバージョンの C++ は必ずやってくる。 • プログラミング言語がどのように進化・改良されているか、 言語設計の思想を学ぶことで、プログラミング全般への理解が深まる。 11
新しいコア言語機能 1 size_t 型を表現するリテラルを活用しよう 2 ヘッダの有無を調べたいときは __has_include を使おう 3 状態をもたない関数オブジェクトの operator() を static にしよう 4 コンストラクタに [[nodiscard]] を指定しよう 5 範囲 for 文における一時オブジェクトの寿命に気を付けよう 6 関数の引数をコンパイル時にチェックできる方法を知ろう 7 標準コンセプトを活用しよう 8 制約を使ったときのオーバーロード解決ルールを知ろう 12
新しい標準ライブラリの機能(Range 以外)1/2 9 列挙型から整数型への変換に std::to_underlying() を使おう 10 ある文字や文字列が含まれているかを .contains(x) で調べよう 11 .resize_and_overwrite() で std::string のコストをさらに抑えよう 12 符号無し整数型と符号付き整数型の値の比較を安全に行う方法を知ろう 13 std::optional::value_or() での不要なコストに気を付けよう 14 std::optional に追加されたモナディックな操作を知ろう 15 std::optional で不足するなら std::expected を使おう 16 配列が特定の並びで開始/終了しているかを調べる方法を知ろう 13
新しい標準ライブラリの機能(Range 以外)2/2 17 連続するメモリの範囲は std::span で表現しよう 18 一次元配列を多次元配列のように扱いたいときは std::mdspan を使おう 19 std::format() の書式文字列のコンパイル時エラーチェックを知ろう 20 配列やコンテナの文字列化を std::format() に任せよう 21 ユーザ定義型を std::format() に対応させる方法を知ろう 22 ファイルを排他的に作成する方法を知ろう 23 エラーの調査にスタックトレースオブジェクトを活用しよう 24 非推奨化/削除される機能に注意しよう 14
新しい標準ライブラリの機能(Range)1/2 25 Range アダプタを使ってみよう 26 配列への逆順や部分アクセスに範囲 for 文を使おう 27 Range アダプタのパイプライン記法を使いこなそう 28 ループでインデックス値が必要なときも範囲 for 文が使えることを知ろう 29 複数のコンテナの要素を対応付けて扱うときも範囲 for 文を使おう 30 指定した区切り文字での文字列分割には std::views::split() を使おう 31 複数の集合の組み合わせを 1 つの範囲 for 文で簡単に生成しよう 32 単調増加や繰り返しの Range を手軽に作成する方法を知ろう 15
新しい標準ライブラリの機能(Range)2/2 33 集合を等サイズのグループに分割するときは std::views::chunk() を使おう 34 1 要素ずつずらしながらグループを作るときは std::views::slide() を使おう 35 タプルを持つコンテナの範囲 for 文では必要なメンバアクセスだけ抽出しよう 16
近年の C++ 規格策定の動向 36 C++ の新しい規格が決まる過程 37 最新の C++ 標準化の状況を調べる方法 38 ネットワーク機能と非同期処理の紆余曲折 39 契約プログラミング導入はどうなっている? 40 パターンマッチング導入はどうなっている? 41 リフレクション導入はどうなっている? 42 最新の C++ を先取りする方法 43 ゲーム業界から C++ 標準化委員会への提言 17
1 size_t 型を表現する リテラルを活用しよう 18
size_t 型を表現するリテラルを活用しよう は環境依存の型。 等でのリテラルの扱いに注意が必要だった。 • • size_t の正体候補 • size_t は int ではないのでコンパイルエラー size_t が unsigned long でない環境でコンパイルエラー OK だが、明示的な size_t の記述が必要 19
size_t 型を表現するリテラルを活用しよう C++23 から、 型の整数リテラルを表現するサフィックス が追加。 型の使い勝手が基本型に近づいた。 どちらも 型 型 20
2 ヘッダの有無を調べたいときは __has_include を使おう 21
ヘッダの有無を調べたいときは __has_include を使おう あるヘッダの存在の有無に応じて使用するコードを分けたいときがある。 • 【例 1】新しい C++ 標準ライブラリのヘッダが存在するかどうか • 【例 2】サードパーティーライブラリのヘッダに対してパスが通っているか C++17 以降、プリプロセッサ ディレクティブ 〇〇 が使える。 インクルードファイル 〇〇 が存在する場合は 1, 存在しない場合 0 になる。 〇〇 ヘッダファイル 〇〇 が存在する場合 ヘッダファイル 〇〇 が存在しない場合 22
ヘッダの有無を調べたいときは __has_include を使おう 【使用例 1】新しい C++ 標準ライブラリヘッダが無いとき、代替実装を使う。 標準ライブラリ 代替のライブラリ 23
ヘッダの有無を調べたいときは __has_include を使おう 【使用例 2】特定のサードパーティライブラリを利用可能な(ヘッダにパスが通ってい る)とき、拡張機能を提供する。 へのパスが通っている場合 自作クラスを のクラスへ変換する関数 24
3 状態をもたない関数オブジェクトの operator() を static にしよう 25
状態をもたない関数オブジェクトの operator() を static にしよう 関数オブジェクトは関数ポインタに比べてインライン化しやすく、コンパイラの最適化 を促進する。 関数オブジェクト しかし、 はメンバ関数なので、インライン化されなかった場合には、 ポインタを受け渡す追加の小さなコストが生じる。 26
状態をもたない関数オブジェクトの operator() を static にしよう C++23 では が を使わない(状態を持たない)場合、 とし て宣言して最適化を促進できるように。 27
状態をもたない関数オブジェクトの operator() を static にしよう 生成されるコードの変化(GCC, -O2): 28
4 コンストラクタに [[nodiscard]] を指定しよう 29
コンストラクタに [[nodiscard]] を指定しよう C++20 から、通常の関数・メンバ関数だけでなく、コンストラクタにも 属性を付与できるようになった。ほとんどのクラスは「オブジェクト を構築しただけで使わない」ということはないため、 の付与が誤った コードの検出に寄与。特に関数とコンストラクタの取り違え防止に役立つ。 警告 構築したオブジェクトを無視 30
5 範囲 for 文における 一時オブジェクトの寿命に気を付けよう 31
範囲 for 文における一時オブジェクトの寿命に気を付けよう 一時オブジェクトに対する直接的な範囲 for ループを記述したとき、一時オブジェクト の寿命はループの終了まで延長される。次のコードは期待通り動く。 32
範囲 for 文における一時オブジェクトの寿命に気を付けよう しかし C++20 時点では、一時オブジェクトそのものではなく、内部の要素に対する範 囲 for ループを記述した場合、元の一時オブジェクトの寿命は延長されず、未定義動作 (ダングリング)となっていた。 33
範囲 for 文における一時オブジェクトの寿命に気を付けよう C++20 およびそれ以前では、この問題を回避するために、一時オブジェクトを変数で受 け、寿命を延長させる必要があった。 寿命がループ終了まで延長 34
範囲 for 文における一時オブジェクトの寿命に気を付けよう C++23 では仕様が変更され、 ■■ の ■■ で生じた一時オブ ジェクトの寿命は、ループの終了まで延長されるようになった。 C++23 からは、範囲 for 文を書く際の注意事項が減った。 OK に 35
6 関数の引数をコンパイル時に チェックできる方法を知ろう 36
関数の引数をコンパイル時にチェックできる方法を知ろう 関数の引数が必ずコンパイル時定数として記述される場合、その値の妥当性もコンパイ ル時にチェックできると、コーディングミスを早期発見でき嬉しい。 【例】0~100 の範囲の引数を渡さないといけない関数: 実行時エラー 実行時エラー リテラルはコンパイル時定数なので、 コンパイル時にチェックする余地があるはず 37
関数の引数をコンパイル時にチェックできる方法を知ろう 指定された関数やコンストラクタは必ずコンパイル時評価され、途中でコ ンパイル時評価できないコード( など)に達するとコンパイルエラーになる。こ れを利用して引数をラップしたクラスを作ると、コンパイル時引数チェックが実現。 コンパイル時エラー コンパイル時エラー 38
7 標準コンセプトを活用しよう 39
標準コンセプトを活用しよう C++20 から、コンセプトを使用してテンプレートに型制約を与えられるようになった。 テンプレートの意図を明確にし、コードの安全性と可読性を向上させられる。 標準ライブラリで提供されているコンセプトの例: 【例 1】整数型を示すコンセプト 40
標準コンセプトを活用しよう 【例 2】浮動小数点数型を示すコンセプト や や のみを許容し、 を禁止できる。 41
標準コンセプトを活用しよう 【例 3】乱数生成器を示すコンセプト 第二引数には や ほか、自作した乱数生成器ク ラスのみを渡せるよう制約。 次のように乱数生成器を渡さないオーバーロードとも共存可能。 配列から 個ランダムに取得する 乱数生成器ではなく 整数を渡すオーバーロード 42
8 制約を使ったときの オーバーロード解決ルールを知ろう 43
制約を使ったときのオーバーロード解決ルールを知ろう 制約付き関数のオーバーロード解決にあたっては、2 つの基本ルールがある。 【ルール 1】 からなる制約では、より多く制約しているものが選ばれる。 果物 赤い 果物 コンパイルエラー 44
制約を使ったときのオーバーロード解決ルールを知ろう 【ルール 2】 からなる制約では、最も制約の少ないものが選ばれる。 果物 赤い 果物 45
9 列挙型から整数型への変換に std::to_underlying() を使おう 46
列挙型から整数型への変換に std::to_underlying() を使おう 列挙型の値 から整数値を得るとき、これまでは 整数型 がよく使 われていたが、次のようなトラブルが起こりえた。 ① があり、 基底型が → 以上の列挙子が追加された 以上の列挙子の変換が不正確に。警告なし。 ② を 基底型が → に変更され、 で変換していたが、 で変換していたが、 に変更された 以上を変換すると負の値に。警告なし。 47
列挙型から整数型への変換に std::to_underlying() を使おう 基底型を調べる と組み合わせて することで、不正な型への変換を防げたが、記述が長かった。 C++23 では にまとめ、不正な変換防止と簡潔さを両立。 は の基底型 48
列挙型から整数型への変換に std::to_underlying() を使おう 【よくあるパターン】列挙子に整数を足して別の列挙子を得る: の値はデフォルトでは 演算ができない。 で整数 化して計算し、再び元の型に戻す。 49
10 ある文字や文字列が含まれているかを .contains(x) で調べよう 50
ある文字や文字列が含まれているかを .contains(x) で調べよう C++23 では、 や の文字列中に、ある文字や文字 列が含まれているかを調べるメンバ関数 が追加された。 との比較が必要だった従来の方法に比べ、直感的な記述になった。 記述が冗長 シンプル 51
11 .resize_and_overwrite() で std::string のコストをさらに抑えよう 52
.resize_and_overwrite() で std::string のコストをさらに抑えよう には隠れたコストが存在する。 【問】次のような機能をもつ関数をどのように実装する? • • 例: を 回繰り返した文字列を返す → 53
.resize_and_overwrite() で std::string のコストをさらに抑えよう 【解答例】(30 点)ループで毎回 を毎回チェックするうえ、足りない場合はメモリを再確保 54
.resize_and_overwrite() で std::string のコストをさらに抑えよう 【解答例】(60 点) しておく 必要なメモリをあらかじめ確保しておく を毎回チェックする 55
.resize_and_overwrite() で std::string のコストをさらに抑えよう 【解答例】(90 点) してからの 必要なメモリをあらかじめ確保しておく あとで上書きするのに要素は 埋めされる 56
.resize_and_overwrite() で std::string のコストをさらに抑えよう 【解答例】(100 点)C++23 の を使用 57
.resize_and_overwrite() で std::string のコストをさらに抑えよう は、 を指定された長さでリサイズする際、要素 をゼロで初期化する代わりに、ユーザが渡した関数を使って初期値を埋める。 より低レイヤへのアクセスで、取り除くのが困難だったコストを削減できる。 必要なキャパシティ ここでは 200 ラムダ式 キャパシティ分確保されたバッファの先頭ポインタ。 バッファの要素は不定値状態なので、プログラムで値を書き込む 最終的な文字列の長さ(capacity 以下)を報告する ( 以上) 58
12 符号無し整数型と符号付き整数型の 値の比較を安全に行う方法を知ろう 59
符号無し整数型と符号付き整数型の 値の比較を安全に行う方法を知ろう 型の値 vs 型の値の比較は、意図しない暗黙型変換により一方の情報が欠落 することがあり安全でない。警告が発生することもある。 なので警告 60
符号無し整数型と符号付き整数型の 値の比較を安全に行う方法を知ろう C++20 では、異なる整数型の値を安全に比較できる関数が追加された 。 警告は出ない 安全 61
13 std::optional::value_or() での 不要なコストに気を付けよう 62
std::optional::value_or() での 不要なコストに気を付けよう から値を取得する際、有効値を持っていない場合の代わりの値を指定 できるのが 引数 メンバ関数。 は有効値の保持・不保持に関わらず評価されるため、必要のないケースにおいて も必ず評価され( が関数の場合は関数が呼ばれ)、不要なコストが発生しうる。 不要にも関わらず が評価される 見かけの簡潔さに騙されないように 63
std::optional::value_or() での 不要なコストに気を付けよう でのコストを回避するには、少し長くなるが次のように書く。 必要な時だけ が評価される を使っても良い 64
14 std::optional に追加された モナディックな操作を知ろう 65
std::optional に追加されたモナディックな操作を知ろう C++17 で導入された は便利だが、コード内に や を 増やしがちだった。 66
std::optional に追加されたモナディックな操作を知ろう C++23 では、関数型プログラミングでよく使われる「モナド」の考え方を取り入れ、 値への計算を連鎖させる仕組みを提供。 値への一連の操作をチェーン化し、 や の記述回数を減らす。 67
std::optional に追加されたモナディックな操作を知ろう 有効値を保持していれば、 を返す関数 有効値を保持していなければ を返す。 有効値を保持していれば、通常の関数 に値を渡した結果を返す。 を値に適用した結果を 納して返す。 有効値を保持していなければ に格 を返す。 有効値を保持していれば何もしない。 有効値を保持していなければ、 の結果を として返す。 68
15 std::optional で不足するなら std::expected を使おう 69
std::optional で不足するなら std::expected を使おう C++17 から提供された 値を返す関数について、エラー時には無効値 は「無効値」という状態を導入。 を返せるようになった。 失敗時 70
std::optional で不足するなら std::expected を使おう しかし、 はエラーの原因等、詳細を返すことができなかった。 に不正な文字 結果が整数オーバーフロー エラーはすべて nullopt で表現 エラーを参照で受け取るといった方法が必要だった。 71
std::optional で不足するなら std::expected を使おう C++23 の は「正常値またはエラー値」を格納する。 指定した型でエラー値を表現できるようになった。 に不正な文字 結果が整数オーバーフロー 72
std::optional で不足するなら std::expected を使おう の特徴 • ヒープから動的にメモリを確保しない • 正常値とエラー値はメモリを共有する • 通常、 <= ( と似たインタフェースを持ち、習得が容易 • • • • • • • 73
16 配列が特定の並びで開始/終了 しているかを調べる方法を知ろう 74
配列が特定の並びで開始/終了 しているかを調べる方法を知ろう 「配列が特定の要素の並びで始まっているか/終わっているか」を調べる処理は、配列 の長さに注意が必要で、少し複雑だった。 【例】バイナリデータのヘッダ判定(バイト列の先頭 3 バイトの確認): バイナリデータ パターン 配列が指定したパターンで始まっているか 75
配列が特定の並びで開始/終了 しているかを調べる方法を知ろう C++20 では に が入った。 C++23 では汎用バージョンの が提供される。 配列 パターン 配列が指定したパターンで始まっているか 76
17 連続するメモリの範囲は std::span で表現しよう 77
連続するメモリの範囲は std::span で表現しよう メモリ連続な範囲は、配列や std::vector などさまざまな型で表現される。 それらを関数に渡す場合、ポインタと要素数の 2 つを渡すパターンが使われた。 78
連続するメモリの範囲は std::span で表現しよう は の配列版。メモリ連続な範囲を一貫した方法 で受け渡しできるようになる。 への の有無で要素の変更可否も制御できる。 79
18 一次元配列を多次元配列のように 扱いたいときは std::mdspan を使おう 80
一次元配列を多次元配列のように 扱いたいときは std::mdspan を使おう 画像などの二次元配列的な情報を一次元配列上で表現する場合、指定した要素にアクセ スする際のインデックスの計算が面倒だった。 81
一次元配列を多次元配列のように 扱いたいときは std::mdspan を使おう C++23 の を使うと、一次元配列を二次元配列や三次元配列であるかの ように見せるインタフェースを持つビューが提供される。 サイズがコンパイル時定数の場合 C++23 から [] 内に複数記述可能 サイズが実行時に決まる場合 82
19 std::format() の書式文字列の コンパイル時エラーチェックを知ろう 83
std::format() の書式文字列の コンパイル時エラーチェックを知ろう C++20 の は、書式文字列(文字列リテラルなどの定数式)をコンパ イル時計算でパースする( 6 も参照)。書式の誤りを、実行する前にコンパイルエ ラーとして検出できるため、コード実行の安全性が高まる。 エラー エラー エラー エラー エラー 不正な括弧 引数の不足 引数の不足 インデックス指定の有無の混在 引数の型の不一致 84
20 配列やコンテナの文字列化を std::format() に任せよう 85
配列やコンテナの文字列化を std::format() に任せよう これまで や を文字列化する標準の方法は用意されていなかっ たため、自前での変換が必要だった。 86
配列やコンテナの文字列化を std::format() に任せよう C++23 から、配列や各種コンテナを で文字列化できるようになった。 87
21 ユーザ定義型を std::format() に 対応させる方法を知ろう 88
ユーザ定義型を std::format() に対応させる方法を知ろう これまでの C++ では、ユーザ定義型を出力ストリームに対応させる場合、 に対する をオーバーロードした。 89
ユーザ定義型を std::format() に対応させる方法を知ろう C++20 の へ対応するには、 の特殊化を実装する。 {:.4} のようなオプション書式にも 対応したい場合、ここに詳細な実装 90
ユーザ定義型を std::format() に対応させる方法を知ろう に対応することで書けるコードの例: 91
22 ファイルを排他的に 作成する方法を知ろう 92
ファイルを排他的に作成する方法を知ろう 「ファイルへの書き込みをするが、既にファイルがあれば書き込みはしない」という排 他的アクセスを実現したいとき、次のように書くと、(1) の瞬間に外部からファイルを作 成されて不適切な競合(TOCTOU)が生じる余地があった。 ファイルの存在をチェック ファイルがすでに存在しています。 書き込み処理 93
ファイルを排他的に作成する方法を知ろう このような競合を防ぐため、排他モードのフラグとして、POSIX には Windows API には , などが用意されている。C++23 からは、C++ 標準 ファイルストリームにも フラグが追加された。 ファイルがすでに存在しています。 がこの関数で新規作成されたファイルであることが保証されている 書き込み処理 94
23 エラーの調査にスタックトレース オブジェクトを活用しよう 95
エラーの調査にスタックトレースオブジェクトを活用しよう デバッガをアタッチしていない環境で問題が発生した場合、 得られないことがある。C++23 で導入された標準の では十分な情報を ライブラリを使 うことで、一貫した方法でスタックトレースを取得できるようになった。 96
24 非推奨化/削除される 機能に注意しよう 97
非推奨化/削除される機能に注意しよう C++ で昔から提供されている標準ライブラリ機能について、次のような理由で別の方法 への置き換えが望ましくなることがある。 • 使い勝手や安全性に問題がある • 存在理由がほとんどない • コア言語機能で対応可能になった 上記の理由がとくに強い場合、段階を踏んで C++ の仕様から削除される。 ステップ 1: 非推奨化(deprecated)し、使用時に警告やエラーを出す 特定のマクロを定義することで警告をオプトアウトできる場合もある ステップ 2: 標準ライブラリから削除し、完全に使えなくなる 98
非推奨化/削除される機能に注意しよう 具体例と対処方法 ① は C++11 で非推奨化、C++17 で削除。 → を使う。 スマートポインタの前身 は、ムーブセマンティクスが無い時代の設計の ため、コピー演算子の挙動が非直感的で間違いやすかった。 は C++17 で非推奨化、C++20 で削除。 → を使う。 間違いやすさの解消や、標準ライブラリの他の機能との名前・使い勝手の一貫性のため。 99
非推奨化/削除される機能に注意しよう 具体例と対処方法 ② は C++17 で非推奨化、C++20 で削除。 → を使う。 アルゴリズム関数等に渡す、 変引数テンプレートを使った を返す関数(predicate)を反転させる。C++11 の可 が任意個の引数に対応し、不要になった。 は C++17 で非推奨化、C++26 で削除。 → 代替手段無し。自前の Unicode 変換ライブラリを用いる。 標準で規定された仕様が安全ではなく、別の実装のほうが好ましいと判断された。 100
25 Range アダプタを使ってみよう 101
Range アダプタを使ってみよう Range アダプタは従来の範囲 for 文をパワーアップし、変換、フィルタリング、サブ セット化などの操作を可能にする。具体的には Range(配列やコンテナ)から新しい ビューを生成し、そのビューへの範囲 for 文を書ける。 【例】 入力された Range を逆順にアクセスするビューを作る。 ビューは遅延評価。ここでは逆順に並び替えた新しい配列を作るわけではなく、必要な ときに評価され、余分なオーバーヘッドは生じない。 102
Range アダプタを使ってみよう 【例】 入力された Range の各要素を、指定した関数で変換した結果のビューを作る。 【例】 入力された Range のうち、指定した条件を満たす要素だけからなるビューを作る。 ビューは値として扱える 103
26 配列への逆順や部分アクセスに 範囲 for 文を使おう 104
配列への逆順や部分アクセスに範囲 for 文を使おう C++17 まで、範囲 文は全要素へ順番にアクセスするときにしか使えず、逆順や部 分配列では従来のループを記述する必要があった。 逆順 最初の つをスキップ 105
配列への逆順や部分アクセスに範囲 for 文を使おう C++20 / C++23 の Range アダプタを使うと、次のような変則的なアクセスパターンを、 実行時効率を落とさずに範囲 文で書けるようになる。 逆順 部分配列 N 個おき 106
配列への逆順や部分アクセスに範囲 for 文を使おう 逆順 最初の つをスキップ 個おき 107
27 Range アダプタの パイプライン記法を使いこなそう 108
Range アダプタのパイプライン記法を使いこなそう C++20 / C++23 の Range アダプタは、 演算子によるパイプライン記法で重ねがけす ることで、より複雑なビューを表現できる。 逆順 + 部分配列 部分配列 + 部分配列 逆順 + N 個おき 109
Range アダプタのパイプライン記法を使いこなそう 逆順 つ 最初の つをスキップ 逆順 個おき つ 110
28 ループでインデックス値が必要なときも 範囲 for 文が使えることを知ろう 111
ループでインデックス値が必要なときも 範囲 for 文が使えることを知ろう C++17 では、範囲 for 文でインデックス値を使いたい場合、範囲 for 文の外で変数を 作る必要があった。 112
ループでインデックス値が必要なときも 範囲 for 文が使えることを知ろう C++20 では、範囲 文の 内で変数を宣言できるようになり、変数のスコープを ループ内に制限できるようになった。 113
ループでインデックス値が必要なときも 範囲 for 文が使えることを知ろう C++23 の は、配列の要素とインデックス値をペアにした ビューを提供する。構造化束縛を用いて次のように記述する。 114
29 複数のコンテナの要素を対応付けて 扱うときも範囲 for 文を使おう 115
複数のコンテナの要素を対応付けて 扱うときも範囲 for 文を使おう 2 つのコンテナについて、同時に先頭から順にアクセスし、それぞれの要素を組として 処理するパターンは、範囲 文だけで書くことができなかった。 少ないほうに合わせる 116
複数のコンテナの要素を対応付けて 扱うときも範囲 for 文を使おう は、複数の Range の要素を対応付けた からなるビュー を生成する。コンテナが複数あっても範囲 for 文が使えるようになった。 ループ回数は少ないほうに合わせる 2 3 "b" "c" 4 117
30 指定した区切り文字での文字列分割には std::views::split() を使おう 118
指定した区切り文字での文字列分割には std::views::split() を使おう 設定ファイルのパース時など、文字列を指定した区切り文字で分割したいことがある。 Range アダプタ を使うことで、追加のメモリの確保無しで、 区切り後の結果に順にアクセスするビューを生成する。 119
指定した区切り文字での文字列分割には std::views::split() を使おう の引数には文字列を指定することもできる。ただし、文字列 リテラルを直接使うと、末尾のヌル文字までをも区切り文字列として扱ってしまう(例 えば は「 」)。代わりに リテラルを使うとよい。 120
31 複数の集合の組み合わせを 1 つの範囲 for 文で簡単に生成しよう 121
複数の集合の組み合わせを 1 つの範囲 for 文で簡単に生成しよう 複数の集合に対して、要素の組み合わせをすべて列挙する場合、範囲 文を集合の個 数だけネストさせる必要があった。 【例】2 つの集合 { " ", " ", " ", " " } と { 1, 2, …, 13 } から、すべてのトラン プのカードを作る。 1 2 … 12 13 122
複数の集合の組み合わせを 1 つの範囲 for 文で簡単に生成しよう C++23 の は、複数の集合に対する組み合わ せの全パターン(直積)を、1 つの範囲 for 文で挙げるビューを生成する。 1 2 … 12 13 123
32 単調増加や繰り返しの Range を 手軽に作成する方法を知ろう 124
単調増加や繰り返しの Range を 手軽に作成する方法を知ろう は、 から始まり 未満まで 1 ずつ単調増加していく数 列を表現する Range を生成する。 125
単調増加や繰り返しの Range を 手軽に作成する方法を知ろう 第二引数を省略して や にすると、無限長の数列になる。 と組み合わせて、有限の Range として評価する。 126
単調増加や繰り返しの Range を 手軽に作成する方法を知ろう は、 を 回繰り返す Range を生成する。 127
単調増加や繰り返しの Range を 手軽に作成する方法を知ろう 第二引数を省略して にすると、無限回繰り返す。 と組み合わせて、有限の Range として評価する。 128
33 集合を等サイズのグループに分割する ときは std::views::chunk() を使おう 129
集合を等サイズのグループに分割する ときは std::views::chunk() を使おう 配列の先頭から N 個ごとに切り分ける処理を、説明的なコードにするのは難しかった。 【例】所持している 28 個の素材(配列)に対して先頭から 5 個ずつグループ化して関 数に渡す(5 回 を呼ぶ) 130
集合を等サイズのグループに分割する ときは std::views::chunk() を使おう 通常のループを使って書いた例: 131
集合を等サイズのグループに分割する ときは std::views::chunk() を使おう C++23 の は、集合を N 個ずつに切り分けた一連の部分配 列へのビューを生成する。N 個に満たない最後の余りも含まれる点には注意が必要。 132
集合を等サイズのグループに分割する ときは std::views::chunk() を使おう 対象となる要素数を なり、 であらかじめ切り出すと、余りが出なく を取り除ける。 133
34 1 要素ずつずらしながらグループを作る ときは std::views::slide() を使おう 134
1 要素ずつずらしながらグループを作るときは std::views::slide() を使おう 配列の先頭 N 個でグループを作り、先頭が抜ける代わりに後ろが入ってグループを作り 直していく操作を、説明的なコードにするのは難しかった。 【例】8 人のパーティーメンバーの先頭から 4 人のグループを作り、1 人が抜けては後 ろの 1 人がグループに加入する(5 回 を呼ぶ) 135
1 要素ずつずらしながらグループを作るときは std::views::slide() を使おう 通常のループを使って書いた例: 136
1 要素ずつずらしながらグループを作るときは std::views::slide() を使おう C++23 の を使うと、先頭から 1 要素ずつずらしながら N 要素で作るグループをすべて挙げるビューを簡単に生成できる。 137
35 タプルを持つコンテナの範囲 for 文では 必要なメンバアクセスだけ抽出しよう 138
タプルを持つコンテナの範囲 for 文では 必要なメンバアクセスだけ抽出しよう ペアやタプルの配列の走査で、一部のメンバにだけアクセスしたい場合、ループ本体で 無関係のメンバにアクセスしないようにするのは注意力が要求される。 139
タプルを持つコンテナの範囲 for 文では 必要なメンバアクセスだけ抽出しよう 構造化束縛を利用しても、注意が必要なのは変わらない。 140
タプルを持つコンテナの範囲 for 文では 必要なメンバアクセスだけ抽出しよう C++20 の を使うと、タプルのうち 番目のメンバの みにアクセスするビューを生成でき、不要なメンバへのアクセスを避けられる。 にアクセス 141
タプルを持つコンテナの範囲 for 文では 必要なメンバアクセスだけ抽出しよう 連想コンテナ用に • と同じ • と同じ があり、「 のみ」「 のみ」のアクセスも直感的に記述できる。 全要素のキーのみにアクセス 142
タプルを持つコンテナの範囲 for 文では 必要なメンバアクセスだけ抽出しよう 抽出したビューに対しても、新しい Range アダプタをつなげられる。 6 文字以上のアイテムを、 0 から始まるインデックスとペアで列挙 143
近年の C++ 規格策定の動向 144
36 C++ の新しい規格が決まる過程 145
C++ の新しい規格が決まる過程 国際標準規格として発行 (3 年ごと) 承認 規格ドラフトにマージ C++ 標準化委員会 承認 CWG / LWG: 仕様文言を洗練させる 承認 EWG / LEWG: 設計を妥当なものにする 番号付き文書の提出・会議での発表 フォーラム、メーリングリストでの議論 専門家の集まるフォーラムに投稿 何かを思いつく 出典: https://isocpp.org/std/the-committee 146
37 最新の C++ 標準化の状況を調べる方法 147
最新の C++ 標準化の状況を調べる方法 • 新しいバージョンで追加された機能を学ぶ • cpprefjp • cppreference.com • 提案されている内容や、議論の状況を調べる • C++ 標準化委員会リポジトリの Issue トラッカー github.com/cplusplus/papers/issues • 提案ごとに Issue が立てられ、提案のカテゴリや状態、会議での投票結果などが記 録されている • 提案文書の日本語訳を公開しているブログ 講演者の 1 人が書いています • onihusube.hatenablog.com/archive/category/WG21 148
38 ネットワーク機能と 非同期処理の紆余曲折 149
ネットワーク機能と非同期処理の紆余曲折 • C++20 をターゲットに、標準ライブラリへのネットワーク機能が進んでいた。 • 一定の実績があったライブラリ Boost.Asio と、そこで使われていた非同期処理機構 Executor をセットで標準に導入しようとした。 • 2019 年夏、標準の Executor 提案(提案番号 P0443)の設計が完了し、C++23 には 入りそうな見込みとなった。 • Boost.Asio も、P0443 を前提としたネットワークライブラリをリリース、C++ 標準 化委員会でも P0443 に基づいたネットワークライブラリの作業が始まった。 • ところが、2021 年 6 月、P0443 を大きく改良した新 Executor ライブラリ(提案番 号 P2300)が提案された 150
ネットワーク機能と非同期処理の紆余曲折 非同期処理を実行する場所(ハードウェア)を表現するもの 引数無し戻り値なしの非同期処理 非同期処理の開始、結果の受信とエラーハンドリングの方法が無い 151
ネットワーク機能と非同期処理の紆余曲折 非同期処理を実行する方法と完了する場所を表すもの 非同期処理グラフとその実行場所を表現するもの scheduler をパイプラインに組み込むことで、 sender が処理を実行する場所を表現する sender を受け、その処理を現在のスレッドで実行。 進捗をコールバックでき、結果やエラーをハンドルできる 非同期処理の開始、完了を待機し完了後に結果を取り出して返す 152
ネットワーク機能と非同期処理の紆余曲折 • C++ 的には、突如出てきた新提案 P2300 のほうが明確に優れていた(sender / receiver という抽象化モデルをコアに据え、汎用性が高まった) • 旧提案 P0433 をキャンセルし、新提案 P2300 を C++ 標準の Executor に採用しよ うという機運が高まった。 • そうすると、これまで旧提案 P0443 の Executor に基づいて開発してきた標準ネット ワークライブラリは、再設計を余儀なくされることに • 新提案 Executor P2300 は 2024 年 7 月に C++26 の規格案に正式にマージ • 一方、標準ネットワークライブラリのほうは、P2300 の Executor ベースで再度作り 直しに。標準ライブラリ入りは、もう少し時間がかかりそう 153
39 契約プログラミング導入は どうなっている? 154
契約プログラミング導入はどうなっている? • 2013 年頃から、C++ に契約プログラミングを導入する機運が高まる。 • 2018 年 6 月、契約プログラミング機能が C++20 に向けて採択される。 • 属性構文による事前 / 事後条件およびアサーションの指定(契約注釈) • ビルドモードによる契約注釈のセマンティクスの一括制御 • 契約条件を評価するかしないか / 契約違反が発生した際に継続するかしないか • 契約違反発生時は違反ハンドラによって最低限のメッセージを表示 • ビルドモードによって、その後継続するかが異なる 155
契約プログラミング導入はどうなっている? • しかし、C++20 への採択が決まったあと、細かい仕様を詰めていこうとしたら、いろ いろ問題があることが明らかになった • 契約違反が起きた後で継続できる必要はあるのか? • 契約注釈のセマンティクスを個別制御したい場合はどうするのか? • 「コンパイラが注釈を最適化のヒントとして利用できる」ことと、「契約が破られ た後も実行を継続するオプション」が競合(想定していないコードが実行され、未 定義動作を引き起こす) • 議論は紛糾し、2019 年 7 月には「C++20 までに間に合わない」となり、一旦 C++20 への採択を取り消すことに 156
契約プログラミング導入はどうなっている? • 2020 年~2023 年にかけ、コツコツと再設計が行われてきた。 • 契約プログラミングの基本部分を改めて固め直した: • 契約のための専用構文を、属性構文ではなく通常の構文に変更した。 • • 契約注釈の 4 種類のセマンティクス • コンパイラオプションとの対応 • 違反ハンドラのカスタマイズ • 現在も、機能追加要望や設計への疑問点がアクティブに議論されている。 • C++26 に間に合うかは微妙なところ。間に合わなかったら C++29。 157
契約プログラミング導入はどうなっている? 事前条件 事前条件 事前条件 事後条件 が指定された契約条件はチェックされない(最適化に利用される) アサーション アサーション 158
契約プログラミング導入はどうなっている? 事前条件 事前条件 事前条件 事後条件 契約注釈はコードの仮定とされない アサーション アサーション 159
40 パターンマッチング導入は どうなっている? 160
パターンマッチング導入はどうなっている? • 2015 年頃から、C++ へのパターンマッチングの導入が検討されてきた • 2020 年に 式を使うパターンマッチングの文法案が固まる(P1371) • 2021 年、別案として キーワードを使ったパターンマッチング構文および キーワードを使った C++ 文法の拡張案が発表される(P2392) • 2022 年、P1371 を修正し、 P2688 が提案された( 式の代わりに 式と を用いた改善案 をパターンマッチングの文脈外でも使用できる) • 現在 P2392 と P2688 の 2 つの案が有望であるが、どちらの案も詳細が流動的であり、 最終判断を下せる状況には進んでいない。 • おそらく C++29 になりそう。 161
パターンマッチング導入はどうなっている? のマッチング例 162
パターンマッチング導入はどうなっている? のマッチング例 163
41 リフレクション導入はどうなっている? 164
リフレクション導入はどうなっている? • 2020 年、リフレクションの初期案(Reflection TS: N4856)が一旦まとまった。 • テンプレートメタプログラミングのように、リフレクション情報を「型」として扱う ものだった。 • 一方で、C++20 での定数式実行まわりの言語機能強化を踏まえ、リフレクション情報 を「値」として扱い、定数式(通常の関数)で処理する方向性を望む声も高まった。 • Reflection TS を初期案として、改めて C++20 の機能を踏まえた設計を始めた。 • 2023 年 10 月頃、値ベースのリフレクションの初稿(P2996)が完成。 • C++ コンパイラ(Clang, EDG)開発者による実験実装も公開された。 • 順調にいけば 2024 年秋頃には C++26 への採択が決まる見込み 165
リフレクション導入はどうなっている? 列挙値を文字列に変換する例 166
42 最新の C++ を先取りする方法 167
最新の C++ を先取りする方法 {fmt} (https://fmt.dev/)は のもとになったライブラリ。 や の C++26 相当の機能までを実装し、C++11 環境で動かすことができる。 168
最新の C++ を先取りする方法 range-v3 は C++20 以降の や に含まれるレンジライブラリ を先行実装したライブラリ。C++14 環境で使うことができる。 169
最新の C++ を先取りする方法 GSL (C++ Core Guideline Support Library) に含まれる は、C++20 の の基になったライブラリ。 170
最新の C++ を先取りする方法 オンラインコンパイラ「Compiler Explorer」では、様々なコンパイラを選ぶことがで き、C++ の新機能を試験実装しているブランチも含まれる。契約プログラミングの例: 事前条件 事前条件 事前条件 事後条件 アサーション アサーション 171
43 ゲーム業界から C++ 標準化委員会への提言 172
ゲーム業界から C++ 標準化委員会への提言 • 2023 年、モントリオールのゲーム開発者らにより、C++ をゲーム開発でより使いや すい言語にしていくためのレポート(提案番号 P2966)が発表された。 【扱っているトピック】コンパイル時計算、動的メモリ確保を回避 したライブラリ機能、有用な属性、ムーブセマンティクスの改善、 例外機構の改善、パターンマッチング、C++ ツールの改善、ネット ワーク機能、並行プログラミング、ロギング及び I/O ライブラリ、 数値計算、その他 • レポート内では、こうしたゲーム業界による取り組みの周知や、レポートの内容につ いての業界内外からのフィードバックを呼びかけている。 • 「C++ はここがダメ」「C++ がこうなればいいのに」と思っているそこのあなた、 声を届けるチャンスです! 173
最新の C++ 情報にキャッチアップする方法 ◆ 新機能を知る • C++ 言語機能 | cpprefjp.github.io/lang.html • cppreference.com(英語) | en.cppreference.com/w/ ◆ 最新規格を扱っている本を探す • C++ 書籍 | cppmap.github.io/learn/books/ ◆ 提案文書を読む • 提案文書 | cppmap.github.io/standardization/proposals/ ◆ YouTube • C++ Weekly | @cppweekly 174
C++23 の機能を使える最新のコンパイラ ◆ コンパイラオプション • GCC 11- / Clang 13- : -std=c++2b • Visual Studio 2022 : /std:c++latest • Xcode : -std=c++2b ◆ コンパイラごとの、実装されている C++20 / 23 機能一覧表 en.cppreference.com/w/cpp/compiler_support 175
謝辞 ◆ 機能解説 監修 • 高橋 晶 さん (cpprefjp) • 川崎 洋平 さん (cpprefjp) ◆ Thanks • cpprefjp スポンサー・コントリビュータの皆様 • cppmap スポンサー・コントリビュータの皆様 (サンプルコードの一部は cpprefjp / cppmap の記事を引用しています) • 講演リハーサルに参加してくださった皆様 176
★ 質問 / 事前に寄せられた質問 177
(質問と回答は、会場からの質問も含めたうえで、後日追加収録します) 178
更新履歴 2024-08-23 • 22 (p.94) C++20 を C++23 に修正 2024-08-22 • 12 (p.61) 表内の | 欄の誤記を修正 179