Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CEDEC2020

Ryo Suzuki
September 01, 2020

 CEDEC2020

ゲーム開発者のための C++11~C++20, 将来の C++ の展望
【CEDEC ページ】https://cedec.cesa.or.jp/2020/session/detail/s5e8327a52702c.html
【解説付きの書籍版 📚】https://zenn.dev/tetsurom/books/cpp11-cpp20-for-game-developers

Ryo Suzuki

September 01, 2020
Tweet

More Decks by Ryo Suzuki

Other Decks in Programming

Transcript

  1. 鈴木 遼 早稲田大学 | cppmap 松村 哲郎 cpprefjp 安藤 弘晃

    cpprefjp ゲーム開発者のための C++11~ C++20, 将来の C++ の展望 v1.2 2020-09-19
  2. 最新の C++ を解説する オープンソースの日本語 Web サイトを作っています cpprefjp cpprefjp.github.io • 標準ライブラリや言語機能のリファレンスとサンプル

    • 各規格における新機能のリストアップ cppmap cppmap.github.io • C++20 の新機能、C++23 以降に計画されている機能 • C++ の書籍やイベント、開発ツールなどの情報 2
  3. C++ に強くなる 60 分! ◆ 前半: ゲーム開発に役立つ C++11 ~ C++20

    の機能 • C++11 / C++14 / C++17 / C++20 を経て大きくアップデートした C++, ゲームやツール開発に役立つ機能をピックアップし、42 個のガイドラインに • 講演を聞くことで、モダンな知識とセンスで C++ を書けるように ◆ 後半: C++ の仕様はどのように決まる? • C++ の規格がどのように決まるのかを解説 • 講演を聞くことで、C++ 標準化の流れを追えるようになり、将来の C++ の進化を 見据えたソフトウェア設計・API 設計ができるように • C++ を便利にするために C++ の標準化に参加する方法もガイド 3
  4. 過去の関連資料 ゲーム開発者のための C++11/C++14 鈴木 遼 (早稲田大学) 全日本学生ゲーム開発者連合(全ゲ連)第 14 回交流会 厳選

    0b1010個! C99/C11/C++11/C++14/C++17 の 今すぐ使えるコーディングTipsと応用事例 山之内 毅 (株式会社ポリフォニー・デジタル) CEDEC KYUSHU 2018 4
  5. ① ゲーム開発に役立つ C++11 ~ C++20 の機能 • 便利な機能 • コードの書き方が変わる機能

    • 実行時性能が向上する機能 • 今後に期待の機能 ② C++ の仕様はどのように決まる? • ゲーム開発と C++ 標準化 • C++ に Issue を送ってみた体験談 • 国内 WG 委員インタビュー • ゲーム開発に関連する、議論が進行中の提案 • 最新の C++ 情報にキャッチアップ 5
  6. 1 値を一定の範囲に収めるときは std::clamp を使おう 2 標準ライブラリの数学定数を使おう 3 std::string に追加された便利な関数を知ろう 4

    連想コンテナ内のキーの有無の確認を contains で明快にしよう 5 列挙型名が何度も登場するコードを using enum で短くしよう 6 コンテナから要素を削除する、一貫性のある方法を知ろう 7 変数未使用の警告を抑制する方法を知ろう 便利な機能 1/2 6
  7. 21 クラステンプレートのテンプレート引数推論を活用しよう 22 無効値を表現したいときに std::optional を使おう 23 型安全な共用体を std::variant で実現しよう

    24 テンプレートの型に応じた処理には constexpr if を使おう 25 テンプレートパラメータにコンセプトで制約を加えよう 26 カスタマイゼーションポイントオブジェクト (CPO) を優先して使おう コードの書き方が変わる機能 2/2 9
  8. 27 文字列を std::string_view で受け渡ししよう 28 ポインタのバイト境界をコンパイラに伝えよう 29 配列要素の効率的なシフト操作を知ろう 30 非順序連想コンテナをもっと効率よく使おう

    31 ステートレスなメンバ変数のメモリ消費をゼロにしよう 32 分岐の起こりやすさをコンパイラに伝える方法を知ろう 実行時性能が向上する機能 1/2 10
  9. 33 数値 → 文字列の変換には std::to_chars を使おう 34 文字列 → 数値の変換には

    std::from_chars を使おう 35 洗練された文字列フォーマット機能を使おう 36 コンパイル時に実行できる標準ライブラリ関数の操作を知ろう 37 コンパイル時と実行時で処理を分ける方法を知ろう 38 新しいアロケータを知ろう 実行時性能が向上する機能 2/2 11
  10. 値を一定の範囲に収めるときは std::clamp を使おう std::clamp(x, min, max) は、値 x を min

    以上、max 以下に収めた値を返す。 void Compress(int quality) { // quality を 0 ~ 100 の範囲に収める quality = clamp(quality, 0, 100); ... } void Compress(int quality) { // quality を 0 ~ 100 の範囲に収める quality = min(max(quality, 0), 100); ... } 14
  11. 標準ライブラリの数学定数を使おう これまで C++ の標準ライブラリには数学定数が無かった(M_PI は拡張)。 C++20 では <numbers> ヘッダに、数学定数の変数テンプレート宣言と、それらの double

    型への特殊化が定義される。π, 1/π, √2, √3, φ (黄金数) など 13 種類。 変数テンプレートなので、float, double どちらでもキャストを書かずに使える。 int main() { using std::numbers::pi; using std::numbers::pi_v; cout << 2.0 * pi << '\n'; // double 型の円周率 cout << pi_v<float> << '\n'; // float 型の円周率 } 16
  12. std::string に追加された便利な関数を知ろう C++20 から、std::string に文字列の先頭や末尾が、ある文字列と一致するかを調べ られるメンバ関数 .starts_with(), .ends_with() が追加された。 int

    main() { string path = "test.png"; cout << boolalpha; cout << path.ends_with(".png") << '\n'; // true cout << path.ends_with(".jpg") << '\n'; // false string url = "https://cpprefjp.github.io/"; cout << url.starts_with("https://") << '\n'; // true } 18
  13. 連想コンテナ内のキーの有無の確認を contains で明快にしよう C++17 までは、連想コンテナ (map, unordered_map など) のキーの有無のチェック にはメンバ関数

    .find() を使った。.find() はキーが存在する場合はその要素へのイ テレータ、存在しない場合は終端イテレータを返すため、m.find(key) != m.end() というコードになるが、これは「有無を調べる」という意図が明快ではない。 int main() { unorderd_map<string, int> table = {{ "one", 1 }, { "two", 2 }}; // 意図が明快でない if (table.find("one") != table.end()) cout << "one というキーを持つ要素が存在\n"; } 20
  14. 連想コンテナ内のキーの有無の確認を contains で明快にしよう 連想コンテナの新しいメンバ関数 .contains(key) は、あるキー key を持つ要素が存 在するかを bool

    値で返す。「有無を調べる」という意図が明快になる。 ただし、.find() の戻り値のイテレータを再利用して値の変更などをする場合、 .find() のほうが効率が良い。 int main() { unorderd_map<string, int> table = {{ "one", 1 }, { "two", 2 }}; // 意図がわかりやすい if (table.contains("one")) cout << "one というキーを持つ要素が存在\n"; } 21
  15. 列挙型名が何度も登場するコードを using enum で短くしよう enum class は、switch で列挙型の名前を繰り返し書くのが冗長。 enum class

    ImageFormat { BMP, PNG, JPEG, DDS }; Image Decode(ImageFormat format) { switch (format){ case ImageFormat::BMP: return DecodeBMP(); case ImageFormat::PNG: return DecodePNG(); case ImageFormat::JPEG: return DecodeJPEG(); case ImageFormat::DDS: return DecodeDDS(); default: return{}; } } 23
  16. 列挙型名が何度も登場するコードを using enum で短くしよう C++20 の using enum 宣言を使うと、そのローカルスコープでスコープ解決演算子を 省略して列挙子を使えるようになる。

    enum class ImageFormat { BMP, PNG, JPEG, DDS }; Image Decode(ImageFormat format) { switch (format){ using enum ImageFormat; case BMP: return DecodeBMP(); case PNG: return DecodePNG(); case JPEG: return DecodeJPEG(); case DDS: return DecodeDDS(); default: return{}; } } 24
  17. コンテナから要素を削除する、一貫性のある方法を知ろう auto LessThan10 = [](int n){ return n < 10;

    }; int main() { vector<int> v = { ... }; list<int> li = { ... }; unordered_map<string, int> m = { ... }; v.erase(remove_if(v.begin(), v.end(), LessThan10), v.end()); li.remove_if(LessThan10); for (auto it = m.begin(); it != m.end();) if (LessThan10(it->second)) it = m.erase(it); else ++it; } コンテナから条件を満たす要素を削除する方法は、コンテナの種類によって最適な書き 方が異なる。vector は std::remove_if() とメンバ関数の .erase() 。list はメ ンバ関数 .remove_if() 。unordered_map はイテレータを使ったループで削除。 26
  18. コンテナから要素を削除する、一貫性のある方法を知ろう auto LessThan10 = [](int n){ return n < 10;

    }; int main() { vector<int> v = { ... }; list<int> li = { ... }; unordered_map<string, int> m = { ... }; erase_if(v, LessThan10); erase_if(li, LessThan10); erase_if(m, [](const auto& p) { return LessThan10(p.second); }); } C++20 では、すべての標準コンテナに対して最適な方法で要素を削除する、一貫して使 用可能な非メンバ関数 std::erase(), std::erase_if() が追加された。 27
  19. 変数未使用の警告を抑制する方法を知ろう struct CPlayer : ICharacter { void update([[maybe_unused]] double deltaTime,

    int a, int b) { [[maybe_unused]] const int c = (a + b); assert(c > 1); walk(); } }; オーバーライドしたメンバ関数で使わない引数や、assert 内でしか使わない変数は、 変数未使用の警告の要因になる。未使用でも問題ないことをコンパイラに伝える [[maybe_unused]] 属性を付けることで警告を抑制できる。 29 リリースビルドでは c が使われなくなる
  20. ビット操作のための便利な関数を使おう int main() { // 2 の累乗数か? cout << has_single_bit(512u)

    << '\n'; // true // 2 の累乗数に切り捨て cout << bit_floor(1023u) << '\n'; // 512 // 2 の累乗数に切り上げ cout << bit_ceil(1023u) << '\n'; // 1024 // その数の表現に最低何ビット必要か? cout << bit_width(31u) << '\n'; // 5 cout << bit_width(32u) << '\n'; // 6 } ビット操作のための関数を提供する <bit> ヘッダが追加。 31
  21. ビット操作のための便利な関数を使おう int main() { const uint16_t x = 0b0001'0110'1110'1111; //

    左にビットローテートした値を返す cout << rotl(x, 2) << '\n'; // 23484 (0b0101'1011'1011'1100) // 右にビットローテートした値を返す cout << rotr(x, 2) << '\n'; // 50619 (0b1100'0101'1011'1011) cout << countl_zero(x) << '\n'; // 3 (最上位ビットから連続する 0 の個数) cout << countr_one(x) << '\n'; // 4 (最下位ビットから連続する 1 の個数) cout << popcount(x) << '\n'; // 10 (1 になるビットの個数) } ビット列を循環シフトさせるビットローテーションや、1 になっているビットを数える std::popcount() なども。対応する専用の CPU 命令があればマッピングされる 32
  22. ビット操作のための便利な関数を使おう union FloatUint32 { float f; uint32_t u; }; int

    main() { float f = 0.5f; uint32_t u1 = *reinterpret_cast<uint32_t*>(&f); // 未定義動作 uint32_t u2 = FloatUint32{ f }.u; // 未定義動作 uint32_t u3; memcpy(&u3, &f, sizeof(f)); // コピーするのは OK } ある型のオブジェクトが格納されているバイト列に対して、別の型のオブジェクトと解 釈してアクセスする type punning は C++ では未定義動作 (C 言語では OK)。 33
  23. C++20 ではバイト列の再解釈を合法的に行える std::bit_cast() が追加。 自作クラスへの bit_cast() 応用例 ビット操作のための便利な関数を使おう int main()

    { float f = 0.5f; uint32_t u = bit_cast<uint32_t>(f); // OK } struct Color { uint8_t r, g, b, a; }; constexpr bool operator ==(Color a, Color b) { return bit_cast<uint32_t>(a) == bit_cast<uint32_t>(b); // 比較が高速に } // 厳密には Color が 4 バイトになる保証はないが、 // bit_cast はサイズの不一致を検出してエラーにしてくれる 34
  24. 配列にもコンテナにも使える、 サイズ、イテレータ取得関数を使おう テンプレートで任意の範囲を受け取り、メンバ関数 .size(), .begin(), .end() で そのサイズやイテレータを取得しようとすると、配列型を受け付けない。 template <class

    Container> void F(Container& c) { size_t n = c.size(); fill(c.begin(), c.end(), 0); } int main() { vector<int> v = { 1, 2, 3 }; int a[4] = { 10, 20, 30, 40 }; F(v); // OK F(a); // 配列は .size() などのメンバ関数を持たないのでコンパイルエラー } 36 配列にはメンバ関数が無い!
  25. 配列にもコンテナにも使える、 サイズ、イテレータ取得関数を使おう C++11 で、コンテナも配列も一貫して使える非メンバ関数 begin() / end(), C++17 で size(),

    data(), empty(), C++20 で ssize() が追加された。 template <class Container> void F(Container& c) { size_t n = size(c); int64_t i = ssize(c); // サイズを符号付き整数型で得たい場合に便利 fill(begin(c), end(c), 0); } int main() { vector<int> v = { 1, 2, 3 }; int a[4] = { 10, 20, 30, 40 }; F(v); // OK F(a); // OK } 37 配列でも OK
  26. ソースコード上の位置情報を手軽に扱おう void MyFunc() { constexpr auto s = source_location::current(); //

    ここで情報を取得 cout << s.file_name() << '\n'; // ファイル名 cout << s.line() << '\n'; // 行番号 cout << s.column() << '\n'; // 列番号(何文字目) cout << s.function_name() << '\n'; // 関数名 } デバッグ時には、ソースコードのファイル名、行番号、関数名などをプログラムで取得 できると便利だが、C++17 までは __FILE__, __LINE__, __func__ などの C 言語 由来マクロを使う必要があった。C++20 で追加された std::source_location クラ スは、単体でこれらの情報をまとめて管理できる。 39
  27. オブジェクトのバイト列は std::byte で表現しよう int main() { vector<unsigned char> data =

    LoadData(); data[0] &= 1; // わかる data[1] += data[2]; // こういう操作はあり? data[2] = 10; // 数値の配列と勘違いしていない? data[3] *= 3; // マジ? } オブジェクトのバイト列を unsigned char や char で表現すると、数値・文字として の操作を許すことになるため、誤った扱いをする原因になる。 41
  28. オブジェクトのバイト列は std::byte で表現しよう int main() { vector<byte> data = LoadData();

    data[0] &= 1; // OK // data[1] += data[2]; // ビット演算以外は定義されないのでエラーに // data[2] = 10; // 数値型は暗黙的に std::byte には変換されないのでエラーに data[2] = byte{ 0x0A }; // OK // data[3] *= 3; // ビット演算以外は定義されないのでエラーに } C++17 ではバイト表現に特化した 1 バイトの std::byte 型 (enum class : unsigned char として実装) が用意され、ビット演算のみが定義される。std::byte 型の配列を使うことで、そのデータがバイト列であるという意図を明確に表現できる。 42
  29. 範囲を 1 つの引数で渡すアルゴリズム関数を使おう int main() { vector<int> v = {

    1, 5, 3, 2, 4 }; auto b = count_if(v.begin(), v.end(), [](int n){ return n % 2 == 0; }); int max = *max_element(v.begin(), v.end()); sort(v.begin(), v.end()); fill(v.begin(), v.end(), 5); } イテレータのペアではなく、範囲(string や vector, 配列など)を単体で渡せるアルゴ リズム関数(オブジェクト)が追加され、見通しの良いコードを書けるようになった。 int main() { vector<int> v = { 1, 5, 3, 2, 4 }; auto b = ranges::count_if(v, [](int n){ return n % 2 == 0; }); int max = ranges::max(v); ranges::sort(v); ranges::fill(v, 5); } 44
  30. 標準のファイルとディレクトリ操作を使おう namespace fs = std::filesystem; int main() { // ファイルが存在するかをチェック

    if (fs::exists("test.txt")) fs::copy("test.txt", "test2.txt"); // ファイルをコピー // ファイルを読み込む前に、ファイルのサイズを取得 cout << fs::file_size("save.dat") << '\n'; // フォルダを作成する fs::create_directories("aaa/bbb/ccc"); // カレントディレクトリに含まれる全ファイルを出力 for (const auto& entry : fs::recursive_directory_iterator("./")) cout << entry.path() << '\n'; } C++17 で <filesystem> ヘッダが標準ライブラリに追加され、ファイルやディレクト リの操作・情報取得をポータブルなコードで実現できるようになった。 46
  31. if, switch の条件式で、初期化を分けて書きたい場合、if や switch よりも前のス コープで変数を作る必要があり、変数のスコープが余計に広くなる問題があった。 初期化付き条件分岐を使って、変数のスコープを狭くしよう int main()

    { auto response = GetResponse(); if (response.status() == 200) { response ... } // まだ response のスコープが存続 auto data = GetData(); switch (data.id()) { data ... } // まだ data のスコープが存続 } 48
  32. C++17 では、初期化と条件式が分離した if() や switch() を書けるようになり、変 数のスコープを最小限にできるようになった。 初期化付き条件分岐を使って、変数のスコープを狭くしよう int main()

    { if (auto response = GetResponse(); response.status() == 200) { response ... // response のスコープは if 内のみ } switch (auto data = GetData(); data.id()) { data ... // data のスコープは switch 内のみ } } 49 if (初期化 ; 条件式) switch (初期化 ; 条件式)
  33. さらに、C++20 では範囲ベース for も初期化式をともなうことができ、ループカウン タのスコープを for { } 内に制限させることができる。 初期化付き条件分岐を使って、変数のスコープを狭くしよう

    int main() { vector<string> ss = { ... }; size_t i = 0; for (const auto& s : ss) cout << i++ << ": " << s << '\n'; // ループのあとでも i が使えてしまう } int main() { vector<string> ss = { ... }; for (size_t i = 0; const auto& s : ss) cout << i++ << ": " << s << '\n'; // i のスコープはループ内のみ } 50
  34. クラスに 6 つの比較演算子を定義するだけで、かなりのコード量が必要になる。 比較演算子の実装を楽にしよう struct Date { int year, month,

    day; bool operator <(const Date& rhs) const { return tie(year, month, day) < tie(rhs.year, rhs.month, rhs.day); } bool operator ==(const Date& rhs) const { return tie(year, month, day) == tie(rhs.year, rhs.month, rhs.day); } bool operator > (const Date& rhs) const { return rhs < *this; } bool operator <=(const Date& rhs) const { return !(rhs < *this); } bool operator >=(const Date& rhs) const { return !(*this < rhs); } bool operator !=(const Date& rhs) const { return !(*this == rhs); } }; 52
  35. C++20 では、三方比較演算子 <=> を定義することで、最大 6 つの比較演算子を自動導 出できる。とくに、クラスの基底と全メンバを宣言順に辞書式比較するだけのケースで は、<=> の実装さえ default

    でコンパイラに任せられる。 <=> を default 以外で実装する場合、<, <=, >, >= の 4 つの比較演算子のみが自動 導出され、== を追加で定義する必要がある。例えば動的配列の同値比較では、最初にサ イズだけを比較するという効率的な実装を提供できる可能性があるためである。 比較演算子の実装を楽にしよう struct Date { int year, month, day; auto operator <=>(const Date&) const = default; }; 53
  36. 構造体のメンバ変数の初期化は、どのメンバに何を設定したのかわかりにくい。 名前を使ってあとから代入すると const にできない。 構造体のメンバ変数を名前でわかりやすく初期化しよう const D3D12_VIEWPORT viewport = {

    0.0f, 0.0f, 1280.0f, 720.0f, 0.0f, 1.0f }; D3D12_VIEWPORT viewport; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; viewport.Width = 1280.0f; viewport.Height = 720.0f; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; 55
  37. C++20 では、集成体のメンバ変数名を指定した初期化 (Designated Initialization)が できるようになる。 指示子の順番はメンバの宣言順と同じにする必要があり、省くと { } 初期化になる。 D3D12_VIEWPORT

    viewport{}; のような従来の集成体初期化を「指示付き初期化子 が 0 個の初期化」と考えると、その挙動を理解しやすい。 構造体のメンバ変数を名前でわかりやすく初期化しよう const D3D12_VIEWPORT viewport = { .TopLeftX = 0.0f, .TopLeftY = 0.0f, .Width = 1280.0f, .Height = 720.0f, .MinDepth = 0.0f, .MaxDepth = 1.0f, }; 56
  38. リテラル (100 や 4.5, "abc" のようにコードに直接書く定数)に何らかの加工を施し たい場合、C++11 以降はリテラル演算子を定義することで見た目をスマートにできる。 リテラル演算子を定義して定数を簡潔に書こう int

    main() { // 90.5° をラジアンに変換したい cout << sin(ToRadians(90.5)) << '\n'; // "HelloHelloHello" を作りたい cout << Repeat("Hello", 3) << '\n'; } int main() { cout << sin(90.5_deg) << '\n'; cout << "Hello"_repeat(3) << '\n'; } 58
  39. C++11 でラムダ式が追加され、関数オブジェクトを手軽に作れるようになった。 しかし、C++11 時点でのキャプチャにはいくつかの制約があった。 柔軟になったラムダ式のキャプチャを活用しよう struct S { vector<int> m_buffer;

    void work(unique_ptr<int> ptr, int n) { bool flag = (n > 3); auto f = [this, // メンバ変数はキャプチャできないので代わりに this をキャプチャ ptr, // move した結果をキャプチャできないのでエラー flag // キャプチャするものはローカル変数として一旦作らないといけない ]() mutable { ... }; } }; int d = 5; auto n = count_if(v.begin(), v.end(), [d](int n){ return n % d == 0; }); 61
  40. C++14 のラムダ式では、任意の式の結果をキャプチャできるようになり、より少ない制 約でラムダ式を作れるようになった。 柔軟になったラムダ式のキャプチャを活用しよう struct S { vector<int> m_buffer; void

    work(unique_ptr<int> ptr, int n) { auto f = [&b = m_buffer, // 新しい参照変数 b でメンバ変数を参照キャプチャ p = move(ptr), // 新しい変数 p を作ってムーブキャプチャ flag = (n > 3) // 新しい変数 flag で式の結果をキャプチャ ]() mutable { ... }; } }; 62
  41. 複数の値は std::pair や std::tuple で返し、 構造化束縛で受け取ろう 関数で複数の値を返したい場合、構造体を作るのが手間であれば、代わりに std::pair (要素が 2

    つ) または std::tuple (要素が 3 つ以上)を使うことができる。C++17 か らは pair や tuple の戻り値を { } だけで構築できる。 pair<double, double> GetTwo() { // return make_pair(35.2, 136.9); // C++14 まで return{ 35.2, 136.9 }; } tuple<double, double, string> GetThree() { // return make_tuple(35.2, 136.9, string("Nagoya")); // C++14 まで return{ 35.2, 136.9, "Nagoya" }; } 64
  42. 複数の値は std::pair や std::tuple で返し、 構造化束縛で受け取ろう pair や tuple の結果はそのままでは扱いづらいため、構造化束縛宣言により名前を付

    けるとよい。構造化束縛は配列や集成体にも使え、auto& や auto&& で受け取ることも できる。 pair<double, double> GetTwo(); tuple<double, double, string> GetThree(); int main() { auto[x, y] = GetTwo(); cout << x << ", " << y << '\n'; auto[lat, lng, name] = GetThree(); cout << lat << ", " << lng << ", " << name << '\n'; } 65
  43. 関数に [[nodiscard]] を付けると、戻り値を誤って無視したときに警告してくれる。 • 戻り値を無視すると呼ぶ意味がない関数に、積極的に付けてよい • 乱数生成関数は? → 内部ステートを更新する副作用あり。戻り値無視も意味がある [[nodiscard]]

    で関数の使い間違いを防ごう struct Float2 { float x, y; [[nodiscard]] Float2 normalized() const; // 正規化した値を返す }; int main() { Float2 v(1.0f, 1.0f); v.normalized(); Use(v); } 67 警告が発生(戻り値を無視)
  44. クラステンプレートのテンプレート引数を、コンストラクタの引数の型から推論できる ようになり、より短いコードでクラステンプレートを使えるようになった。 クラステンプレートのテンプレート引数推論を活用しよう int main() { vector<int> v = {

    1, 2, 3 }; array<double, 3> a = { 0.1, 0.2, 0.3 }; tuple<int, float, string> t = { 30, 0.5f, "hello"s }; mutex m; lock_guard<mutex> lock(m); } int main() { vector v = { 1, 2, 3 }; array a = { 0.1, 0.2, 0.3 }; tuple t = { 30, 0.5f, "hello"s }; mutex m; lock_guard lock(m); } 69
  45. std::optional<Type> を使うと、型 Type が「無効値」も保持できるようになる。サ イズが 0 か 1 の vector<Type>

    と考えるとわかりやすい。有効値を持つかを if (op) や op.has_value() で確認し、*op または op.value() で実際の値を取得する。 無効値を表現したいときに std::optional を使おう optional<int> oi; // デフォルトでは無効値 optional<int> oi2 = 200; // 有効値を与えることもできる if (oi) cout << *oi << '\n'; // 実行されない if (oi2) cout << *oi2 << '\n'; // 実行される: 200 oi = 100; // 有効値を代入 oi2.reset(); // 無効値にする if (oi) cout << *oi << '\n'; // 実行される: 100 if (oi2) cout << *oi2 << '\n'; // 実行されない 71
  46. op.value_or(x) は、有効値を保持している場合はその値を、保持していない場合は代 わりに x を返す。Type と比較可能な型との比較演算も定義される。 無効値を表現したいときに std::optional を使おう optional<int>

    oi; optional<int> oi2 = 200; cout << oi.value_or(-1) << '\n'; // -1 cout << oi2.value_or(-1) << '\n'; // 200 cout << boolalpha; cout << (oi == 500) << '\n'; // false cout << (oi2 > 0) << '\n'; // true 72
  47. 使えそうな場所: ある型のデータがあり、その値が有効か無効かも表現するために、追加 で bool 型の変数を使っている。 無効値を表現したいときに std::optional を使おう Point m_dragStartPos;

    // マウスのドラッグを開始した座標 bool m_isDragging; // ドラッグ中であるか void onMouseDown() { m_dragStartPos = GetCursorPos(); m_isDragging = true; } void onMouseMove() { if (m_isDragging) auto v = (GetCursorPos() – m_dragStartPos); } void onMouseUp() { m_isDragging = false; } 73
  48. optional<Type> を使うことで、有効・無効の情報も合わせて管理できる。 無効値を表現したいときに std::optional を使おう optional<Point> m_dragStartPos; // マウスのドラッグを開始した座標 void

    onMouseDown() { m_dragStartPos = GetCursorPos(); } void onMouseMove() { if (m_dragStartPos) auto v = (GetCursorPos() – *m_dragStartPos); } void onMouseUp() { m_dragStartPos.reset(); } 74
  49. C++17 から、union の代替となる std::variant<...Types> が追加。variant の 変数には、テンプレート引数の型リストに含まれる型のオブジェクトを代入できる。保 持しているオブジェクトのデストラクタが適切に呼ばれ、正しくない型で読み取ろうと した場合には例外を送出するなど、union には無かった型安全性が得られる。

    型安全な共用体を std::variant で実現しよう int main() { variant<string, double, bool> v1 = 3.14, v2 = "Hello"s; if (v1.index() == 1) // 保持している型を型リストのインデックスで返す cout << get<1>(v1) << '\n'; // 3.14 if (holds_alternative<string>(v2)) // 保持してる型が、ある型に一致するかを返す cout << get<string>(v2) << '\n'; // Hello v2 = true; // 保持する型を bool 型の値に変更。string オブジェクトは破棄される cout << v2.index() << '\n'; // 2 } 76
  50. std::visit 関数を使うと、保持してる型に応じて行う処理をシンプルに記述できる。 型安全な共用体を std::variant で実現しよう int main() { variant<string, double,

    bool> v1 = 3.14, v2 = "Hello"s; // ジェネリックラムダを使用する方式 visit([](const auto& x){ cout << x << '\n'; }, v1); // 3.14 // () 演算子をオーバーロードした関数オブジェクトを使用する方式 struct MyVisitor { string operator()(const string& s) const { return s; } string operator()(double d) const { return to_string(d); } string operator()(bool b) const { return b ? "True" : "False"; } }; cout << visit(MyVisitor{}, v2) << '\n'; // Hello } 77
  51. 1 つの関数テンプレートの中に、異なる型を想定したコードを記述すると、実体化の際 にエラーになる。 テンプレートの型に応じた処理には constexpr if を使おう template <class T>

    auto GetValue(T t) { if (is_pointer_v<T>) return *t; // T = int のとき *t はできず、実体化に失敗 else return t; // T = int* のとき、すべての return の型が一致せず、実体化に失敗 } int main() { int a = 100; int* p = &a; cout << GetValue(a) << '\n'; // コンパイルエラー: 実体化に失敗 cout << GetValue(p) << '\n'; // コンパイルエラー: 実体化に失敗 } 79
  52. C++17 の constexpr if を使うと、コンパイル時に条件を満たさない部分を実体化の 対象から除外できる。複数必要だった関数テンプレートの実装を 1 つに簡略化できる。 テンプレートの型に応じた処理には constexpr

    if を使おう template <class T> auto GetValue(T t) { if constexpr (is_pointer_v<T>) // T = int のときは return *t; // 実体化から除外される else // T = int* のときは return t; // 実体化から除外される } int main() { int a = 100; int* p = &a; cout << GetValue(a) << '\n'; // OK: 100 cout << GetValue(p) << '\n'; // OK: 100 } 80
  53. テンプレートの型に制約を加えるには、SFINAE (スフィナエ) と呼ばれる複雑なイディ オムを使う必要があった。 テンプレートパラメータにコンセプトで制約を加えよう template <class T, enable_if_t<is_integral_v<T>>* =

    nullptr> T Mod(T x, T y) { // ↑ T を整数型に制約する return x % y; } template <class T, enable_if_t<is_floating_point_v<T>>* = nullptr> T Mod(T x, T y) { // ↑ T を浮動小数点数型に制約する return fmod(x, y); } int main() { cout << Mod(12, 5) << '\n'; // 2 cout << Mod(1.75, 1.5) << '\n'; // 0.25 } 82
  54. 標準のコンセプトがいくつか用意されているので、それを使えばさらに短くなる。 テンプレートパラメータにコンセプトで制約を加えよう template <integral T> // integral コンセプト T Mod(T

    x, T y) { return x % y; } template <floating_point T> // floating_point コンセプト T Mod(T x, T y) { return fmod(x, y); } int main() { cout << Mod(12, 5) << '\n'; // 2 cout << Mod(1.75, 1.5) << '\n'; // 0.25 } 84
  55. 「このような操作ができる型」という例示によって制約を定義することも可能。 テンプレートパラメータにコンセプトで制約を加えよう template <class T> // 同じ型どうしの + 演算が定義されている型 concept

    Addable = requires (T x) { x + x; }; template <Addable T> auto Add3(T a, T b, T c) { return a + b + c; } template <class T> // .size() メンバ関数を持つ型 concept HasMemberFuntionSize = requires (T x) { x.size(); }; template <HasMemberFuntionSize T> void ShowSize(const T& c) { cout << c.size() << '\n'; } 85
  56. カスタマイゼーションポイント オブジェクト (CPO) を優先して使おう std::swap() や std::begin(), std::end() などの関数テンプレートをユーザ定 義型に対しても正しく機能させるには、「using

    後に非修飾で呼び出す」という、少し 複雑な使い方をする必要がある (詳しくは『Effective C++ 第3版』25 項参照)。 template <class Type> void F(Type& a, Type& b) { ... using std::swap; swap(a, b); } 87
  57. カスタマイゼーションポイント オブジェクト (CPO) を優先して使おう C++20 の標準ライブラリに導入されたカスタマイゼーションポイントオブジェクト (CPO) とよばれるグローバルな関数オブジェクトは、内部実装にコンセプトを使うこと で同様のことを 1

    行で実現してくれる。 コードの短縮や、ユーザ定義型に対する誤った関数呼び出しを避けるために、C++20 以 降では、swap(), begin(), end(), size(), ssize() などは、std::ranges 名 前空間にある CPO を使用すべきである。 template <class Type> void F(Type& a, Type& b) { ... std::ranges::swap(a, b); } 88
  58. 文字列を const std::string& で受け取る関数に、文字列リテラルや const char* を渡すと、std::string 型の一時オブジェクトが作られ、実行時にコストが発生する。 const char*

    を受け取るオーバーロードを作るのも手だが、文字列を受け取るすべて の関数にオーバーロードを用意するのは大変。 文字列を std::string_view で受け渡ししよう void WriteLog(const string& s) { ... } int main() { WriteLog("Hello"); // string 型の一時オブジェクトが作られる const char* s1 = "Modern"; WriteLog(s1); // string 型の一時オブジェクトが作られる string s2 = "C++"; WriteLog(s2); // 参照渡し } 90
  59. C++17 で導入された std::string_view は、すでに作成されている文字列の先頭ポ インタと長さだけを管理する(自分では所有権を持たない)クラスで、std::string, 文字列リテラル、const char* のための効率的な共通インタフェースとして使える。 新しくメモリをアロケーションしないので、実行効率に優れている。 文字列を

    std::string_view で受け渡ししよう void WriteLog(string_view s) { // 値渡しで OK ... s.substr(0, 2); // 部分文字列も string_view で作成できるので効率的 } int main() { WriteLog("Hello"); // 先頭ポインタと長さだけ渡される const char* s1 = "Modern"; WriteLog(s1); // 先頭ポインタと長さだけ渡される string s2 = "C++"; WriteLog(s2); // 先頭ポインタと長さだけ渡される } 91
  60. unordered_map<string, int> では、ルックアップ操作でキーに文字列リテラルを 使うと一時オブジェクトが作られてしまう。string_view を使うこともできない。 非順序連想コンテナをもっと効率よく使おう int main() { unordered_map<string,

    int> table = {{ "one", 1 }, { "two", 2 }}; table.find("three"); // string 型の一時オブジェクトが作られる string_view sv = "two"; table[sv]; // エラー table[string(sv)]; // OK } 98
  61. C++20 でもっと効率よくする手順 1: is_transparent を定義した、比較とハッシュの関数オブジェクトを用意する。 非順序連想コンテナをもっと効率よく使おう struct string_compare { using

    is_transparent = void; bool operator()(string_view key, string_view s) const { return key == s; } }; struct string_hash { using is_transparent = void; using transparent_key_equal = string_compare; using hash_type = hash<string_view>; // helper local type size_t operator()(string_view s) const { return hash_type{}(s); } size_t operator()(const string& s) const { return hash_type{}(s); } size_t operator()(const char* s) const { return hash_type{}(s); } }; 99 ハッシュ関数オブジェクト 比較関数オブジェクト
  62. C++20 でもっと効率よくする手順 2: 用意した関数オブジェクトを unorderd_map のテンプレート引数 Hash, Pred に使う。 非順序連想コンテナをもっと効率よく使おう

    struct string_compare { /* 前ページ */ }; struct string_hash { /* 前ページ */ }; int main() { unordered_map<string, int, string_hash, string_compare> table = {{ "one", 1 }, { "two", 2 }}; table.find("three"); // string 型の一時オブジェクトは作られない string_view sv = "two"; table[sv]; // OK } 100 ここに
  63. 「空の基底クラスは最適化によってサイズ 0 にしてよい」という仕様を利用して、この 問題を回避する Empty Base Optimization (EBO) というテクニックが std::tuple

    や std::unique_ptr の内部実装などでも使われているが、複雑なクラスでは継承が増え てコードの見通しが悪くなる問題があった。 ステートレスなメンバ変数のメモリ消費をゼロにしよう struct Empty {}; // ステートレスなクラス struct X : Empty { // 空の基底クラスは専用のアドレスが不要 int32_t i; }; sizeof(int32_t) になる 103
  64. C++20 では、if や switch-case について、起こりやすい / 起こりにくい分岐を [[likely]], [[unlikely]] 属性でコンパイラに伝えられる。コンパイラはその情

    報をヒントに命令の並び替えなどを行い、高速に実行されるコードを生成できる。GCC に以前から存在した __builtin_expect() のような機能を標準化したもの。 どこでも無条件で最適なコードが生成される魔法の機能ではなく、逆に速度が低下する ような場合もあるため、プロファイリングを注意深く行う必要がある。 分岐の起こりやすさをコンパイラに伝える方法を知ろう void Draw(const vector<Vertex>& v) { if (v.empty()) [[unlikely]] // 起こりにくいというヒントを伝える return; m_context->draw(v.data(), v.size()); } 106
  65. 数値から文字列への変換は基本的な機能である。しかし C++標準ライブラリの関数には、 ロケール考慮やメモリの動的確保、例外送出、(sprintf のような) 書式の解析など、余 分なコストが発生する関数しか存在しなかった。C++17 の std::to_chars() は、そ れらの要素をすべて排除した最速の変換を提供する。文字列の格納先はあらかじめ用意

    し、出力フォーマットは引数で指定する。 数値 → 文字列の変換には std::to_chars を使おう int main() { double x = 3.141593; char buf[numeric_limits<double>::max_digits10]{}; // 変換に成功した場合、ptr は変換後の文字列の終端を指す(null 終端はされない) if (auto [ptr, e] = to_chars(begin(buf), end(buf), x); e == errc{}) cout << string_view(buf, ptr - buf) << '\n'; // 3.141593 if (auto [ptr, e] = to_chars(begin(buf), end(buf), x, chars_format::scientific, 8); e == errc{}) cout << string_view(buf, ptr - buf) << '\n'; // 3.141593e+00 } 108
  66. std::to_chars() と同様に、ロケール非依存、動的メモリ確保なしで文字列 → 数値 の変換を最速で行う std::from_chars() が追加された。 文字列 → 数値の変換には

    std::from_chars を使おう int main() { constexpr char s1[] = "3.14159265358979323"; double r1; if (auto [ptr, e] = from_chars(begin(s1), end(s1), r1); e == errc{}) cout << r1 << '\n'; constexpr char s2[] = "80FFFFFF"; unsigned r2; if (auto [ptr, e] = from_chars(begin(s2), end(s2), r2, 16); e == errc{}) cout << r2 << '\n'; } 110
  67. C++20 では、printf のような読みやすさと、I/O stream の型安全性・拡張性を両立 し、実行時性能も考慮した新しい文字列フォーマット機能が導入される。Python や Rust でも採用されている、{} を使った書式指定を用いる。

    洗練された文字列フォーマット機能を使おう int main() { string a = format("C++{}", 20); // C++20 // 引数のインデックスを指定できる string b = format("{2}/{1}/{0}", 2020, 9, 30); // 30/9/2020 string c = format("{0} {0}", "Hello"); // Hello Hello // 小数のフォーマット、基数の指定 string d = format("π = {:.2f}", 3.14159265); // π = 3.14 string e = format("{:b}", 31); // 11111 string f = format("{:#x}", 255); // 0xff // 左・右・中央寄せ string g = format("{:0>6}", 3); // 000003 string h = format("{:*8}", "OK"); // ***OK*** } 112
  68. std::formatter クラスを特殊化することで、自作のクラスも format の中で使える ようになる。 洗練された文字列フォーマット機能を使おう enum class Color {

    Red, Green, Blue }; constexpr string_view names[] = { "Red", "Green", "Blue" }; template<> struct formatter<Color> : formatter<const char*> { auto format(Color c, format_context& ctx) { return formatter<const char*>::format(names[static_cast<int>(c)], ctx); } }; int main() { cout << format("{} Ocean\n", Color::Blue); // Blue Ocean } 113 formatter 特殊化 Color 型を format で使える
  69. 次のようなリテラル演算子 _fmt を定義してあげると、さらにコンパクトに記述できる。 洗練された文字列フォーマット機能を使おう struct format_string { string_view fmt; template

    <class ...T> auto operator()(T&& ...args) const { return format(fmt, forward<T>(args)...); } }; constexpr format_string operator""_fmt(const char* str, size_t len) { return{ .fmt = { str, len }}; } int main() { cout << "{}, C++{}\n"_fmt("Hello", 20); // Hello, C++20 } 114 _fmt リテラル演算子の定義
  70. コンパイル時に数学関数の結果を得たいとき、遅いが constexpr にできる処理を使い、 実行時に呼ばれる場合には、速いが非 constexpr の標準数学関数を呼びたい。しかし、 これらを使い分ける方法が無かった。 コンパイル時と実行時で処理を分ける方法を知ろう constexpr double

    Sqrt(double x) { if (???) { // コンパイル時か実行時かで処理を変える方法が無い! ... /* 平方根を計算する何らかのアルゴリズム */ return r; } else { return std::sqrt(x); // 非 constexpr } } int main() { constexpr double a = Sqrt(2.0); // コンパイル時定数にしたい double b = Sqrt(Random()); // 実行時は std::sqrt で計算したい } 118
  71. C++20 で追加された std::is_constant_evaluated() は、その呼び出しがコンパ イル時評価の文脈であるか、そうでない(実行時の文脈)かを bool 型で返す。先述の 問題を解決し、コンパイル時に先取りできる仕事を増やしやすくなる。 コンパイル時と実行時で処理を分ける方法を知ろう constexpr

    double Sqrt(double x) { if (is_constant_evaluated()) { ... /* 平方根を計算する何らかのアルゴリズム */ return r; } else { return std::sqrt(x); // 非 constexpr } } int main() { constexpr double a = Sqrt(2.0); // コンパイル時定数: 1.414... double b = Sqrt(Random()); // 実行時は std::sqrt で計算 } 119
  72. 従来のコンテナは、アロケータもコンテナの型情報に含むため、異なるアロケータを使 う同種のコンテナは異なる型になり、データのやり取りが面倒だった。 新しいアロケータを知ろう void F(const vector<int>& v) {} int main()

    { vector<int> v1 = { 1, 2, 3 }; // コンパイルエラー:型が一致しない vector<int, MyAlloc<int>> v2 = v1; // こうすれば OK だが面倒 vector<int, MyAlloc<int>> v3(v1.begin(), v1.end()); // コンパイルエラー F(v3); } 121 vector<int> と vector<int, MyAlloc<int>> は別
  73. C++17 では、ポリモーフィックなアロケータ(多相アロケータ)を利用することで、ア ロケータの詳細を型情報に含まない各種標準コンテナが std::pmr 名前空間に提供され る (std::pmr::vector など)。 実際のメモリ確保・解放は std::pmr::memory_resource

    インタフェースに実装する。 新しいアロケータを知ろう void F(const pmr::vector<int>& v) {} int main() { pmr::vector<int> v1; // デフォルトの memory_resource を使用 MyMemoryResource myMR; // ユーザが実装した memory_resource を使用 pmr::vector<int> v2{ v1, &myMR }; // v1 も v2 も同じ型 F(v1); // OK F(v2); // OK } 122 メモリ確保・解放処理はポインタで渡す v1 も v2 も pmr::vector<int>
  74. 標準の memory_resource の 1 つ、std::pmr::monotonic_buffer_resource は、 そのメモリリソースを使って確保したすべてのオブジェクトのメモリを、そのメモリリ ソースのデストラクタまで解放しないことで、メモリ解放の頻度を減らす。 新しいアロケータを知ろう int

    main() { pmr::monotonic_buffer_resource mono; { pmr::vector<int> v1{ { 1,2,3,4 }, &mono }; } // ここでは v1 のメモリを解放しない { pmr::vector<int> v2{ { 5,6,7,8 }, &mono }; } // ここでは v2 のメモリを解放しない } // ここで v1, v2 のメモリを解放 123
  75. C++20 から、ファイルの内容を展開するだけの #include に代わる新しいファイル分 割の仕組みとしてモジュールが導入された。 モジュールではマクロ定義がそれぞれのモジュールの範囲を越えないため、import 順に よる問題が生じない。コンパイルはおおむね速くなるとされているが、現在の実装では 並列数が多い環境で逆に遅くなるケースも報告されていて、コンパイラの改良に期待で ある。C++

    標準ライブラリのモジュール化も C++23 以降に予定されている。 ヘッダに代わるファイル分割の仕組み「モジュール」 myLib.cpp main.cpp // モジュール宣言 export module myLib; // 関数をエクスポート export void MyFunc() { ... } import myLib;// myLib をインポート int main() { MyFunc(); // インポートした関数を呼ぶ } 125
  76. 呼び出した処理の途中で中断・再開ができるような関数、コルーチンが C++ に導入さ れる。C++20 では、コルーチンライブラリを作るのに必要最低限の言語機能とヘッダが 導入された。アプリケーション開発でコルーチンを十分に活用するには、現時点では非 標準のコルーチンライブラリ (CppCoro など) を使用するか、C++23

    もしくは C++26 で予定されている標準ライブラリによるコルーチンサポートを待つ必要がある。 中断と再開をサポートする関数「コルーチン」 generator<int> Iota(int end) { for (int i = 0; i < end; ++i) co_yield i; // 中断して i を返す。再び呼ばれたらここから再開 } // 終端への到達または co_return が、コルーチンの終了を示す int main() { for (auto x : Iota(10)) cout << x << '\n'; // 0~9 を出力 } 127
  77. 関数の引数や戻り値について、assert のようなチェックを言語機能として行う契約プ ログラミングが C++23 で計画されている。 [[expects]] 属性に事前条件、 [[ensures]] 属性に事後条件を宣言。契約違反のハンドラーはカスタマイズ可能。デ フォルトの動作は規定されないが、従来の

    assert と同じにする処理系が多いだろう。 関数の満たすべき条件をコードに明示的に書き、実行時にチェックしたり、静的解析で 活用したりすることで、より安全なプログラミングが可能になる。とくに事後条件は assert では書きにくかった。コンパイラが最適化のヒントとする応用も考えられる。 assert の進化版「契約プログラミング」 double SqrtChecked(double x) [[expects: x >= 0]] // 引数に対する事前条件 [[ensures r: r >= 0]] // 戻り値に対する事後条件 { return std::sqrt(x); } 129
  78. std::tuple や std::variant などの代数的データ型を扱いやすくするために、他の プログラミング言語にならってパターンマッチングを導入する案が議論されている。 inspect 文 / 式という、より宣言的で構造化された switch

    を用いる。 より柔軟な条件分岐「パターンマッチング」 inspect (s) { "cat": cout << "meow\n"; "dog": cout << "bow-wow\n"; __: cout << "hello\n"; } if (s == "cat") cout << "meow\n"; else if (s == "dog") cout << "bow-wow\n"; else cout << "hello\n"; 131
  79. タプル (std::pair や std::tuple) のマッチングの例: より柔軟な条件分岐「パターンマッチング」 inspect (pos) { [0,

    0]: cout << "原点にある\n"; [x, 0]: cout << "X 軸上にある\n"; [0, y]: cout << "Y 軸上にある\n"; [x, y]: cout << x << ',' << y << '\n'; } auto&& [x, y] = pos; if (x == 0 && y == 0) cout << "原点にある\n"; else if (y == 0) cout << "X 軸上にある\n"; else if (x == 0) cout << "Y 軸上にある\n"; else cout << x << ',' << y << '\n'; 132
  80. まだまだある、C++11~C++20 注目の機能 • スレッドサポート • ジェネリックラムダ • インライン変数 • 最大公約数を求める

    std::gcd(), 最小公倍数を求める std::lcm() • 配列の一部の View をつくる std::span • 任意の型を保持できる std::any クラス • インクルードファイルが存在するか調べる __has_include() • あるコア言語 / ライブラリ機能がコンパイラでサポートされているかを判定する機 能テストマクロ 詳しくは cpprefjp の解説ページを: cpprefjp.github.io/lang.html 134
  81. ① ゲーム開発に役立つ C++11 ~ C++20 の機能 • 便利な機能 • コードの書き方が変わる機能

    • 実行時性能が向上する機能 • 今後に期待な機能 ② C++ の仕様はどのように決まる? • ゲーム開発と C++ 標準化 • C++ に Issue を送ってみた体験談 • 国内 WG 委員インタビュー • ゲーム開発に関連する、議論が進行中の提案 • 最新の C++ 情報にキャッチアップ 135
  82. ゲーム開発と C++ 標準化 ◆ ゲームを開発しやすい言語にするための標準化 ゲームは C++ を活用する主要なジャンルの 1 つ。標準化(仕様策定)に参画して発言

    することで、自分たちにとって使いやすいプログラミング言語にしていくことができる。 ◆ C++ の仕様とゲーム開発 ゲーム開発に関係する C++ の言語や標準ライブラリ機能 (ネットワーク、グラフィッ クス、オーディオ等) が他国主導で決められると、日本のゲームハードウェアの性能を 生かしきれない場合もあり、独自 API 乱立でゲーム開発のハードルが上がる原因に。 C++ の新しい 標準機能です! このハードで使うと問題が あるので使用禁止です。 136
  83. 近年発行された C++ の規格 C++98 初の国際標準化。正式名は ISO/IEC 14882:1998 C++03 C++03 からのマイナーな改訂

    C++11 範囲ベース for, ラムダ式, スマートポインタ, <array>, <random> など C++14 2 進数リテラル、変数テンプレート、[[deprecated]] 属性など C++17 構造化束縛、[[nodiscard]] 属性、<filesystem>, <optional> など C++20 一貫比較、コンセプト、モジュール、<format>, <bit> など C++23 (予定) 標準ライブラリのモジュール対応、コルーチンのライブラリ対応、ネット ワークなど(予定) 8 年 3 年 ごと 138
  84. ( 先行実装可能) 品質向上と進化促進のための 3 年サイクル ◆ C++11 までの進め方 実装する機能を決め、全部準備できたらリリース。→ 統合テストが最後までできず、規

    格ドラフトの不確実性・手戻りも多く C++11 の遅延の原因に(当初は C++0x だった)。 ◆ C++11 以降の進め方 リリース周期を 3 年ごとと決め、新機能は安定 してから規格ドラフトにマージするこ とでドラフトを高品質な状態に維持。1 年前までに間に合った機能をリリース 、スケ ジュールの確実性を向上。ドラフトの手戻りを防ぐことでコンパイラの先行実装も促進。 2018 2019 2020 2021 バグ修正 仕様文言の洗練 新機能 A 新機能 B 新機能 C C++20 ドラフト C++23 ドラフト 139
  85. C++ の規格書と規格ドラフト ◆ 規格書 C++14, C++17 など、発行済みの C++ 国際標準規格の仕 様書。C++17

    では 1600 ページ超。公式版は約 2 万円。 ◆ 規格ドラフト 規格書の一歩手前、標準化委員会による規格ドラフト(レ イアウトなどの調整が未適用、内容はほぼ同等)は無料で 閲覧できる。C++20 の規格ドラフトは wg21.link/n4861 Q: これを読めば C++ を勉強できる? A: ノー。規格書はコンパイラ開発者や標準ライブラリ作者など専門家向けに書かれ ている仕様書。入門書のような解説は含まれていない 140
  86. C++ に提案を送る 2 つの方法 ◆ 番号付き文書 (Papers): コア言語や標準ライブラリの機能を新しく追加・変更する提案や、既存の提案に対する フィードバックを格式ばった書式でまとめた文書。作法があるため、専門家の指導やア ドバイスを受けながら書く。採用されるためには、提案を出したあとも標準化会議での

    発表や提案文書の改訂など数か月~数年単位で関与する必要がある。 これまでの番号付き文書一覧: www.open-std.org/jtc1/sc22/wg21/docs/papers/ ◆ Issue / Defect Report: コア言語・標準ライブラリの規格のバグは Issue または Defect Report (欠陥報告) と して投稿する。件数としては番号付き文書の数倍以上が提出される。ほとんどの場合 メーリングリスト上で完結するため、比較的低コストで C++ に貢献できる。 141
  87. 提案が規格になるまで 出典: https://isocpp.org/std/the-committee 何かを思いつく EWG / LEWG: 設計を妥当なものにする CWG /

    LWG: 仕様文言を洗練させる 規格ドラフトにマージ 国際標準規格として発行 (3 年ごと) 番号付き文書の提出・会議での発表 フォーラム、メーリングリスト等での議論 専門家の集まるフォーラムに投稿 承認 承認 承認 C++ 標準化委員会 142
  88. C++ の提案文書を読んでみよう 1/3 P0769R2 Date: ----- Author: ----- Audience: LWG

    Add shift to <algorithm> I. Introduction This paper proposes adding shift algorithms to the C++ STL which move elements forward or backward in a range of elements. 提案のタイトル 文書番号 + リビジョン番号 提出日 著者 議論を行うワーキンググループ名 概要の説明 143
  89. II. Motivation and Scope Shifting elements forward or backward in

    a range is a basic operation which the STL should allow performing easily. An important use case is time series analysis algorithms used in scientific and financial applications. The scope of the proposal is adding the following function III. Impact on the Standard The only impact on the standard is adding the proposed function templates to <algorithm>. IV. Design Decisions 1) shift_left and shift_right are provided as separate function templates instead of just a single shift function template to maximize performance and minimize compiled code size. Since shifting left and shifting right may have significantly different implementations (as is the case in the sample implementation), implementing both shift directions in a single shift 提案が必要な理由 提案が既存の規格に 及ぼす影響 代替手法と比較して 優れる理由 2/3 144
  90. VI. Proposed Wording - In [algorithm.syn], after the declaration of

    shuffle, add: // [<link to alg.shift>], shift template<class ForwardIterator> constexpr ForwardIterator shift_left( ForwardIterator first, ForwardIterator last, typename iterator_traits<ForwardIterator>::difference_type n ); VII. Revision History R2 - Removed unneeded MoveConstructible requirements. - Make shift_left constexpr. - Improved wording. - Removed extra specification in the proposal text about the order in which VIII. Acknowledgements IX. References 詳しい書き方: wg21.link/n3370 既存の規格書からの差分。 加筆, 削除 リビジョンの変更履歴 謝辞 参考文献 3/3 145
  91. C++ に Issue を送ってみた体験談 1/4 ◆ 規格ドラフトに怪しいところを発見 cpprefjp の記事執筆中、C++20 の規格ドラフトの中に誤りと思われる箇所を発見。

    std::pmr::polymorphic_allocator という標準ライブラリのクラスの 3 つの関数 に [[nodiscard]] が付いていなかった。 ◆ 提案文書を調べた 関連する採択済み文書 P0339R6 と P0600R1 を調べると、前者には当該関数を追加す る提案、後者にはその関数を含む様々な関数に [[nodiscard]] を付ける提案があった。 したがって、これらの提案文書をドラフトにマージする際、誤って見落とされたと判断。 ◆ Issue として報告しようと決めた 関数への [[nodiscard]] の付け忘れは、過去の Issue にも報告があった。今回見つけ た問題についても Issue を投稿してみることに。 146
  92. C++ に Issue を送ってみた体験談 2/4 ◆ 標準化委員会にメールで Issue を投稿 宛先:

    ライブラリワーキンググループの Chair 差出人: Hiroaki Ando 件名: Isn't [[nodiscard]] necessary for allocate function of std::polymorphic_allocator? Discussion: [[nodiscard]] is specified for std::polymorphic_allocator<>::allocate(). But allocate functions added with P0339r6(https://wg21.link/p0339r6) doesn't have it. Isn't [[nodiscard]] necessary for these functions? Proposed resolution: This wording is relative to N4835. Change 20.12.3 Class template polymorphic_allocator [mem.poly.allocator.class]/2 <ins>[[nodiscard]]</ins> void* allocate_bytes(size_t nbytes, size_t alignment = alignof(max_align_t)); void deallocate_bytes(void* p, size_t nbytes, size_t alignment = alignof(max_align_t)); template<class T> <ins>[[nodiscard]]</ins> T* allocate_object(size_t n = 1); template<class T> void deallocate_object(T* p, size_t n = 1); template<class T, class... CtorArgs> <ins>[[nodiscard]]</ins> T* new_object(CtorArgs&&... ctor_args); Change 20.12.3.2 Member functions [mem.poly.allocator.mem] メールを送る前にフォーラムで相談し たほうが良い。 isocpp.org/std/submit-issue 147
  93. C++ に Issue を送ってみた体験談 3/4 ◆ Issue 番号が発行された 2 日後に中の人から返信があり、発行された

    Issue 番号と、ステータスページのリンク が送られてきた。Issue タイトルについてのアドバイス(疑問文ではなく断定にしたほ うが良い)ももらった。 ◆ 次の C++ 会議までは Issue を編集可能 年 3 回開催される C++ 標準化の国際会議の直前までは、報告者がメール経由で Issue の文面をアップデートできる。タイトルを修正してもらった。 ◆ 進捗を眺める 編集可能期間を過ぎると、あとは標準化委員会が議論や作業を進めてくれる。ステータ スページに、どの部会で議論された、誰が承認したといった情報が更新される。 148
  94. C++ に Issue を送ってみた体験談 4/4 ◆ C++20 規格ドラフトに反映! 2019 年

    10 月の Issue 投稿から約 4 か月後、2020 年 2 月の C++ 標準化国際会議で 承認され、ステータスが WP (最新のドラフトへ反映)に。 ◆ (余談)アメリカの C++ 標準化 WG よりも早かった 2019 年 11 月にアメリカの C++ 標準化 WG も同じ問題の一部を報告したが、自分の 報告が先行していて上位互換だったため、自分の Issue が採択された。 149
  95. C++ 標準化に日本から関わっている人たち ~ 国内 WG 委員へのインタビュー ~ 答えてくれた方 2 カ月に一度ぐらい、メンバーの十数人が集まって、各自が興味を持った

    新しい提案の解説をして問題点などを議論しています。 メーリングリストや委員会での議論を通して、提案やコメントを出すこと もあります。 SC22/C++WG 小委員会委員 光成 滋生 さん (サイボウズ・ラボ株式会社) Twitter @herumi e-mail herumi nifty.com Q1. 国内 WG ではどのような活動を? 150
  96. 法人として、ITSCJ 規格賛助員 / 準賛助員に加入する必要があります。 詳細は「ITSCJ > よくあるご質問」ページ www.itscj.ipsj.or.jp/faq/index.html をご覧ください。 Q2.

    国内 WG にはどうすれば参加できますか? 私は 2008 年頃から C++11 の規格を決めるためにアドバイザ(ボラン ティア)として参加を始めました。 詳しい知識を持つ方たちと直接議論できるのは大変勉強になります。 ただし、Q2 で答えたように現在は基本的に法人として参加しなければな らないので 2015 年からは業務の一環として活動しています。 Q3. ボランティアなのですか? 151
  97. 公式には「ITSCJ > 委員会」 www.itscj.ipsj.or.jp/hyojunka/h_sn_member/h_sn_member_01.html から SC 22/C++ WG 小委員会にお問い合わせください。 個人的に、私に直接

    Twitter やメールで質問してくださっても構いませ ん。可能な範囲で対応いたします。 Q4. 国内の一般 C++ ユーザが、国内 WG に意見を送れますか? [[nodiscard]], constexpr, コンセプトなど、コンパイル時にエラーがわ かる機能が増え、ゲーム開発が便利になっていくと思います。ぜひ新しい 機能に注目してください。 Q5. CEDEC 受講者の方へメッセージをお願いします 152
  98. ゲーム開発に関連する、議論が進行中の提案 ◆ 2D グラフィックス 簡易的な 2D 描画機能を標準ライブラリにオプションで組み込む ◆ オーディオ 音声の波形データの再生機能を標準ライブラリに追加

    ◆ 半精度浮動小数点数 画像処理や機械学習で使われる 16-bit 浮動小数点数型のネイティブサポート ◆ flat_set, flat_map ソート済みの配列を使った、空間効率のよい連想コンテナ ◆ 統計ライブラリ 平均や標準偏差など、初歩的な統計処理を行う標準ライブラリ機能 153
  99. 最新の C++ 情報にキャッチアップするには ◆ 新機能を知る • C++ 言語機能 | cpprefjp.github.io/lang.html

    • C++20 の新機能 | cppmap.github.io/standardization/cpp20/ ◆ 最新規格を扱っている本を探す • C++ 書籍 | cppmap.github.io/learn/books/ ◆ 提案文書を読む • 標準化会議 | cppmap.github.io/standardization/meetings/ ◆ C++ の国際カンファレンスの発表映像・スライドを見る • C++ カンファレンス | cppmap.github.io/learn/conferences/ 154
  100. C++20 を使える最新のコンパイラ ◆ コンパイラオプション • GCC 8.1-, Clang 6-, Xcode

    10-: -std=c++2a • GCC 10.1-, Clang 10- : -std=c++20 • Visual Studio 2019: /std:c++latest ◆ コンパイラごとの、実装されている C++20 機能一覧表 en.cppreference.com/w/cpp/compiler_support 155
  101. 謝辞 ◆ 機能解説 監修 高橋 晶 さん (株式会社 Preferred Networks

    / cpprefjp) 川崎 洋平 さん (cpprefjp) ◆ C++ 標準化 取材協力・監修 光成 滋生 さん (サイボウズ・ラボ株式会社 / SC22/C++WG 小委員会委員) ◆ Thanks cpprefjp コントリビュータの皆様、cppmap コントリビュータ・スポンサーの皆様 講演リハーサルのチェックをしてくださった皆様 (サンプルコードの一部は cpprefjp / cppmap の記事を引用しています) 156
  102. ゲーム開発者のための C++11~ C++20, 将来の C++ の展望 ゲーム開発と C++ 標準化 C++

    に Issue を送ってみた体験談 国内 WG 委員インタビュー ゲーム開発に関連する提案 最新の C++ 情報にキャッチアップ 1 値を一定の範囲に収めるときは std::clamp を使おう 2 標準ライブラリの数学定数を使おう 3 std::string に追加された便利な関 数を知ろう 4 連想コンテナ内のキーの有無の確認 を contains で明快にしよう 5 列挙型名が何度も登場するコードを using enum で短くしよう 6 コンテナから要素を削除する、一貫 性のある方法を知ろう 7 変数未使用の警告を抑制する方法を 知ろう 8 ビット操作のための便利な関数を使 おう 9 配列にもコンテナにも使える、サイ ズ、イテレータ取得関数を使おう 10 ソースコード上の位置情報を手軽に 扱おう 11 オブジェクトのバイト列は std::byte で表現しよう 12 範囲を 1 つの引数で渡すアルゴリズ ム関数を使おう 13 標準のファイルとディレクトリ操作 を使おう 14 初期化付き条件分岐を使って、変数 のスコープを狭くしよう 15 比較演算子の実装を楽にしよう 16 構造体のメンバ変数を名前でわかり やすく初期化しよう 17 リテラル演算子を定義して定数を簡 潔に書こう 18 柔軟になったラムダ式のキャプチャ を活用しよう 19 複数の値は std::pair や std::tuple で返し、構造化束縛で受け取ろう 20 [[nodiscard]] で関数の使い間違い を防ごう 21 クラステンプレートのテンプレート 引数推論を活用しよう 22 無効値を表現したいときに std::optional を使おう 23 型安全な共用体を std::variant で実 現しよう 24 テンプレートの型に応じた処理には constexpr if を使おう 25 テンプレートパラメータにコンセプ トで制約を加えよう 26 カスタマイゼーションポイントオブ ジェクト (CPO) を優先して使おう 27 文字列を std::string_view で受け 渡ししよう 28 ポインタのバイト境界をコンパイラ に伝えよう 29 配列要素の効率的なシフト操作を知 ろう 30 非順序連想コンテナをもっと効率よ く使おう 31 ステートレスなメンバ変数のメモリ 消費をゼロにしよう 32 分岐の起こりやすさをコンパイラに 伝える方法を知ろう 33 数値 → 文字列の変換には std::to_chars を使おう 34 文字列 → 数値の変換には std::from_chars を使おう 35 洗練された文字列フォーマット機能 を使おう 36 コンパイル時に実行できる標準ライ ブラリ関数の操作を知ろう 37 コンパイル時と実行時で処理を分け る方法を知ろう 38 新しいアロケータを知ろう 39 ヘッダに代わるファイル分割の仕組 み「モジュール」 40 中断と再開をサポートする関数「コ ルーチン」 41 assert の進化版「契約プログラミン グ」 42 より柔軟な条件分岐「パターンマッ チング」 cpprefjp cpprefjp.github.io cppmap cppmap.github.io 鈴木 遼 @Reputeless 松村 哲郎 @tetzrom 安藤 弘晃 @onihusube9
  103. 質問 / 事前に寄せられた質問 1 Q: std::u8string, u16string, u32string には入出力ストリームや正規表現のラ イブラリサポートが無く、std::string

    へのキャストも明示的にする必要があったりと 非常に残念な存在です。これらの問題は C++23 で改善されると思いますか? A: 正規表現 std::basic_regex を char8_t, char16_t, char32_t にも対応させ る提案 (P1844R1: [LEWGI] Enhancement of regex) が出ていて、今後利用可能になる 可能性はありますが、コンセンサスは得られていません。入出力ストリームや変換に関し ては、そうした文字列の使い勝手を改善するような新しい提案文書はありません。 159
  104. 質問 / 事前に寄せられた質問 2 Q: 構造化束縛で std::ignore のような不要な値をマーキングするものを使えるように なりますか? A:

    C++20 現在、構造化束縛で値を無視することはできません。現在アクティブな提案文 書 P2169R1: [Evolution] A Nice Placeholder With No Name は、アンダースコアの記 号 _ を、汎用的なプレースホルダーとして導入することを提案していて、その用例とし て、構造化束縛での不要な値の無視を挙げています。提案はまだ初期段階で、コア言語の 変更でもあるため、進展には時間がかかると思われます。 160
  105. 質問 / 事前に寄せられた質問 3 Q: 数学関数に特殊関数が追加されたのに、どうして std::complex に対しては実装が ないのですか? A:

    C++17 での特殊関数追加は、GNU C ライブラリなどの独自拡張を C++ にも採用する という流れで入りました。C++ 独自の <complex> ヘッダに関しては、現在のところ大 きな変更をする機運は高まっていません。実数上でしか定義されない特殊関数もあるそう です。 161
  106. 質問 / 事前に寄せられた質問 4 Q: std::is_constant_evaluated() の登場により、数学関数が全て constexpr 化 できそうですが、今後そのようになることはあるでしょうか?

    A: GCC や Clang はコンパイル時に計算できる数学関数を拡張機能として提供していま す。C++ 標準でもそれらを参考に数学関数の一部を constexpr 対応させようという提 案があります (P0533R6: [Library] constexpr for <cmath> and <cstdlib>) 。実行時の 浮動小数点数の丸めモードの影響をどうするかなど、課題がまだあります。 162
  107. 質問 / 事前に寄せられた質問 5 Q: std::function をどれだけカジュアルに使っていいのかわかりません。数バイト程 度のキャプチャーであれば malloc が走らないのは

    emit されるコードを見ればわかりま すが、どれほど規格で保証されているのかがわかりません。 A: 規格ドラフト N4861 - 20.14.16.2.1 には "Implementations should avoid the use of dynamically allocated memory for small callable objects" とあり、最適化すべきとはさ れていますが、保証されるサイズの指定はなく、実装依存です。主要実装では、キャプ チャ無しなら動的メモリ確保は無く、libc++ では 24 バイト, libstdc++ では 16 バイト までなら Small buffer optimization (SBO) が効くようです。 163 参考: quuxplusone.github.io/blog/2019/03/27/design-space-for-std-function/
  108. 質問 / 事前に寄せられた質問 6 Q: 構造化束縛は便利ですが、tuple を使うことによるビルド速度への影響はどうでしょ うか。 A: 複雑なテンプレートを実体化する

    tuple はビルド時間に影響します。しかし、 C++20 では [[no_unique_address]] や、条件付き explicit など、標準の tuple の実装を従来よりも簡潔にする言語機能が入ったため、少しは改善することが期待され ます。コンパイル時間が気になる場合や、メンバに名前を付けておきたい場合は、構造 体を返すのがベターです。 164 参考: cor3ntin.github.io/posts/tuple/
  109. 質問 / 事前に寄せられた質問 7 Q: 浮動小数点数は、コンパイルごとに式の最適化が任意だと聞いています。これにより ゲームの計算結果が異なるため、通信対戦やリプレイ再生に困ります。これを統一する オプションの構想はでていたりするのでしょうか。 A: 同一バイナリを配布したとしても、クライアントごとに計算結果が異なることはさま

    ざまな原因からありえるため、これはプログラムの設計の範疇になると思われます。浮 動小数点数について C++ が保証する範囲は限定的です。これを変更するような提案は現 時点では出ていません。 165 参考: www.jpcert.or.jp/sc-rules/c-flp00-c.html christian-seiler.de/projekte/fpmath/
  110. 質問 / 事前に寄せられた質問 8 Q: C++20 で、まだリリースされるか決まっていない機能はありますか? A: C++20 は

    2020 年 9 月時点で国際標準ドラフトが承認されていて、あとは ISO の承 認を残すだけなので、新たに仕様が変更されることはありません。 166
  111. 質問 / 事前に寄せられた質問 9 Q: std::size は size_t で返しますが,描画 API

    の多くは std::uint32_t で受け 取る関係で警告が出てしまいます。std::size<uint32_t>() のような形は無いので しょうか? A: 残念ながらありません。static_cast を使ってください。 167
  112. 質問 / 事前に寄せられた質問 10 Q: std::format() を試してみようと思ったら、まだ Clang ではサポートされていない んですね。どの機能がコンパイラでいつサポートされるとか、目安はありますか?

    A: C++20 の対応状況は、C++ compiler support (en.cppreference.com/w/cpp/compiler_support) を参照してください。format は、 その提案の原型となった {fmt}(fmt.dev/latest/index.html) というライブラリを使う ことで、先行してほぼ同じ機能を試すことができます。MSVC は 2020 年 9 月時点で、 {fmt} をベースにした実装に着手しているようです。 168
  113. 質問 / 事前に寄せられた質問 11 Q: 以前は JIS の C++ 規格書がありましたが、最近の規格ではどうなっていますか?。

    A: JIS 規格書(日本語)が作成されていたのは C++03 までで、C++11 以降は翻訳の動 きはありません。もし JIS 規格書の作成をするとしたら、日本の WG が作業をすること になるのですが、そもそも規格書はユーザが便利に参照するものとは考えられていない ため、「国産 C++ コンパイラを作る上で、信頼性がある日本語仕様が欲しい」といった ような明確な価値が無い限り、実行に移されることは難しいでしょう。 あなたが信頼性の高い日本語の C++ 情報を必要としているのであれば、そういった取り 組みをしている cpprefjp や cppmap などコミュニティ主導のプロジェクトに貢献・応 援することをぜひ考えてください。 169
  114. 更新履歴 ◆ v1.2 2020-09-19 • [質問 6] – [質問 11]

    追加 • [19] 文言修正 ◆ v1.1 2020-09-05 • [10] const → constexpr • [16] 名前を使って初期化すると → 名前を使ってあとから代入すると • [35] T → T&& • [35] args... → forward<T>(args)... ◆ v1.0 2020-09-02 • 公開 170