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
【解説付きの書籍版 📚】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

    View Slide

  2. 最新の C++ を解説する
    オープンソースの日本語 Web サイトを作っています
    cpprefjp cpprefjp.github.io
    • 標準ライブラリや言語機能のリファレンスとサンプル
    • 各規格における新機能のリストアップ
    cppmap cppmap.github.io
    • C++20 の新機能、C++23 以降に計画されている機能
    • C++ の書籍やイベント、開発ツールなどの情報
    2

    View Slide

  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

    View Slide

  4. 過去の関連資料
    ゲーム開発者のための C++11/C++14
    鈴木 遼 (早稲田大学)
    全日本学生ゲーム開発者連合(全ゲ連)第 14 回交流会
    厳選 0b1010個! C99/C11/C++11/C++14/C++17 の
    今すぐ使えるコーディングTipsと応用事例
    山之内 毅 (株式会社ポリフォニー・デジタル)
    CEDEC KYUSHU 2018
    4

    View Slide

  5. ① ゲーム開発に役立つ C++11 ~ C++20 の機能
    • 便利な機能
    • コードの書き方が変わる機能
    • 実行時性能が向上する機能
    • 今後に期待の機能
    ② C++ の仕様はどのように決まる?
    • ゲーム開発と C++ 標準化
    • C++ に Issue を送ってみた体験談
    • 国内 WG 委員インタビュー
    • ゲーム開発に関連する、議論が進行中の提案
    • 最新の C++ 情報にキャッチアップ
    5

    View Slide

  6. 1 値を一定の範囲に収めるときは std::clamp を使おう
    2 標準ライブラリの数学定数を使おう
    3 std::string に追加された便利な関数を知ろう
    4 連想コンテナ内のキーの有無の確認を contains で明快にしよう
    5 列挙型名が何度も登場するコードを using enum で短くしよう
    6 コンテナから要素を削除する、一貫性のある方法を知ろう
    7 変数未使用の警告を抑制する方法を知ろう
    便利な機能 1/2
    6

    View Slide

  7. 8 ビット操作のための便利な関数を使おう
    9 配列にもコンテナにも使える、サイズ、イテレータ取得関数を使おう
    10 ソースコード上の位置情報を手軽に扱おう
    11 オブジェクトのバイト列は std::byte で表現しよう
    12 範囲を 1 つの引数で渡すアルゴリズム関数を使おう
    13 標準のファイルとディレクトリ操作を使おう
    便利な機能 2/2
    7

    View Slide

  8. 14 初期化付き条件分岐を使って、変数のスコープを狭くしよう
    15 比較演算子の実装を楽にしよう
    16 構造体のメンバ変数を名前でわかりやすく初期化しよう
    17 リテラル演算子を定義して定数を簡潔に書こう
    18 柔軟になったラムダ式のキャプチャを活用しよう
    19 複数の値は std::pair や std::tuple で返し、構造化束縛で受け取ろう
    20 [[nodiscard]] で関数の使い間違いを防ごう
    コードの書き方が変わる機能 1/2
    8

    View Slide

  9. 21 クラステンプレートのテンプレート引数推論を活用しよう
    22 無効値を表現したいときに std::optional を使おう
    23 型安全な共用体を std::variant で実現しよう
    24 テンプレートの型に応じた処理には constexpr if を使おう
    25 テンプレートパラメータにコンセプトで制約を加えよう
    26 カスタマイゼーションポイントオブジェクト (CPO) を優先して使おう
    コードの書き方が変わる機能 2/2
    9

    View Slide

  10. 27 文字列を std::string_view で受け渡ししよう
    28 ポインタのバイト境界をコンパイラに伝えよう
    29 配列要素の効率的なシフト操作を知ろう
    30 非順序連想コンテナをもっと効率よく使おう
    31 ステートレスなメンバ変数のメモリ消費をゼロにしよう
    32 分岐の起こりやすさをコンパイラに伝える方法を知ろう
    実行時性能が向上する機能 1/2
    10

    View Slide

  11. 33 数値 → 文字列の変換には std::to_chars を使おう
    34 文字列 → 数値の変換には std::from_chars を使おう
    35 洗練された文字列フォーマット機能を使おう
    36 コンパイル時に実行できる標準ライブラリ関数の操作を知ろう
    37 コンパイル時と実行時で処理を分ける方法を知ろう
    38 新しいアロケータを知ろう
    実行時性能が向上する機能 2/2
    11

    View Slide

  12. 39 ヘッダに代わるファイル分割の仕組み「モジュール」
    40 中断と再開をサポートする関数「コルーチン」
    41 assert の進化版「契約プログラミング」
    42 より柔軟な条件分岐「パターンマッチング」
    今後に期待の機能
    12

    View Slide

  13. 値を一定の範囲に収めるときは
    std::clamp を使おう
    1
    13

    View Slide

  14. 値を一定の範囲に収めるときは 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

    View Slide

  15. 標準ライブラリの数学定数を使おう
    2
    15

    View Slide

  16. 標準ライブラリの数学定数を使おう
    これまで C++ の標準ライブラリには数学定数が無かった(M_PI は拡張)。
    C++20 では ヘッダに、数学定数の変数テンプレート宣言と、それらの
    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 << '\n'; // float 型の円周率
    }
    16

    View Slide

  17. std::string に追加された
    便利な関数を知ろう
    3
    17

    View Slide

  18. 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

    View Slide

  19. 連想コンテナ内のキーの有無の確認を
    contains で明快にしよう
    4
    19

    View Slide

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

    View Slide

  21. 連想コンテナ内のキーの有無の確認を
    contains で明快にしよう
    連想コンテナの新しいメンバ関数 .contains(key) は、あるキー key を持つ要素が存
    在するかを bool 値で返す。「有無を調べる」という意図が明快になる。
    ただし、.find() の戻り値のイテレータを再利用して値の変更などをする場合、
    .find() のほうが効率が良い。
    int main() {
    unorderd_map table = {{ "one", 1 }, { "two", 2 }};
    // 意図がわかりやすい
    if (table.contains("one"))
    cout << "one というキーを持つ要素が存在\n";
    }
    21

    View Slide

  22. 列挙型名が何度も登場するコードを
    using enum で短くしよう
    5
    22

    View Slide

  23. 列挙型名が何度も登場するコードを
    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

    View Slide

  24. 列挙型名が何度も登場するコードを
    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

    View Slide

  25. コンテナから要素を削除する、
    一貫性のある方法を知ろう
    6
    25

    View Slide

  26. コンテナから要素を削除する、一貫性のある方法を知ろう
    auto LessThan10 = [](int n){ return n < 10; };
    int main() {
    vector v = { ... };
    list li = { ... };
    unordered_map 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

    View Slide

  27. コンテナから要素を削除する、一貫性のある方法を知ろう
    auto LessThan10 = [](int n){ return n < 10; };
    int main() {
    vector v = { ... };
    list li = { ... };
    unordered_map 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

    View Slide

  28. 変数未使用の警告を
    抑制する方法を知ろう
    7
    28

    View Slide

  29. 変数未使用の警告を抑制する方法を知ろう
    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 が使われなくなる

    View Slide

  30. ビット操作のための
    便利な関数を使おう
    8
    30

    View Slide

  31. ビット操作のための便利な関数を使おう
    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
    }
    ビット操作のための関数を提供する ヘッダが追加。
    31

    View Slide

  32. ビット操作のための便利な関数を使おう
    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

    View Slide

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

    View Slide

  34. C++20 ではバイト列の再解釈を合法的に行える std::bit_cast() が追加。
    自作クラスへの bit_cast() 応用例
    ビット操作のための便利な関数を使おう
    int main() {
    float f = 0.5f;
    uint32_t u = bit_cast(f); // OK
    }
    struct Color { uint8_t r, g, b, a; };
    constexpr bool operator ==(Color a, Color b) {
    return bit_cast(a) == bit_cast(b); // 比較が高速に
    }
    // 厳密には Color が 4 バイトになる保証はないが、
    // bit_cast はサイズの不一致を検出してエラーにしてくれる
    34

    View Slide

  35. 配列にもコンテナにも使える、
    サイズ、イテレータ取得関数を使おう
    9
    35

    View Slide

  36. 配列にもコンテナにも使える、
    サイズ、イテレータ取得関数を使おう
    テンプレートで任意の範囲を受け取り、メンバ関数 .size(), .begin(), .end() で
    そのサイズやイテレータを取得しようとすると、配列型を受け付けない。
    template
    void F(Container& c) {
    size_t n = c.size();
    fill(c.begin(), c.end(), 0);
    }
    int main() {
    vector v = { 1, 2, 3 };
    int a[4] = { 10, 20, 30, 40 };
    F(v); // OK
    F(a); // 配列は .size() などのメンバ関数を持たないのでコンパイルエラー
    }
    36
    配列にはメンバ関数が無い!

    View Slide

  37. 配列にもコンテナにも使える、
    サイズ、イテレータ取得関数を使おう
    C++11 で、コンテナも配列も一貫して使える非メンバ関数 begin() / end(), C++17
    で size(), data(), empty(), C++20 で ssize() が追加された。
    template
    void F(Container& c) {
    size_t n = size(c);
    int64_t i = ssize(c); // サイズを符号付き整数型で得たい場合に便利
    fill(begin(c), end(c), 0);
    }
    int main() {
    vector v = { 1, 2, 3 };
    int a[4] = { 10, 20, 30, 40 };
    F(v); // OK
    F(a); // OK
    } 37
    配列でも OK

    View Slide

  38. ソースコード上の位置情報を
    手軽に扱おう
    10
    38

    View Slide

  39. ソースコード上の位置情報を手軽に扱おう
    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

    View Slide

  40. オブジェクトのバイト列は
    std::byte で表現しよう
    11
    40

    View Slide

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

    View Slide

  42. オブジェクトのバイト列は std::byte で表現しよう
    int main() {
    vector 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

    View Slide

  43. 範囲を 1 つの引数で渡す
    アルゴリズム関数を使おう
    12
    43

    View Slide

  44. 範囲を 1 つの引数で渡すアルゴリズム関数を使おう
    int main() {
    vector 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 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

    View Slide

  45. 標準のファイルと
    ディレクトリ操作を使おう
    13
    45

    View Slide

  46. 標準のファイルとディレクトリ操作を使おう
    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 で ヘッダが標準ライブラリに追加され、ファイルやディレクト
    リの操作・情報取得をポータブルなコードで実現できるようになった。
    46

    View Slide

  47. 初期化付き条件分岐を使って、
    変数のスコープを狭くしよう
    14
    47

    View Slide

  48. if, switch の条件式で、初期化を分けて書きたい場合、if や switch よりも前のス
    コープで変数を作る必要があり、変数のスコープが余計に広くなる問題があった。
    初期化付き条件分岐を使って、変数のスコープを狭くしよう
    int main() {
    auto response = GetResponse();
    if (response.status() == 200) {
    response ...
    }
    // まだ response のスコープが存続
    auto data = GetData();
    switch (data.id()) {
    data ...
    }
    // まだ data のスコープが存続
    } 48

    View Slide

  49. 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 (初期化 ; 条件式)

    View Slide

  50. さらに、C++20 では範囲ベース for も初期化式をともなうことができ、ループカウン
    タのスコープを for { } 内に制限させることができる。
    初期化付き条件分岐を使って、変数のスコープを狭くしよう
    int main() {
    vector ss = { ... };
    size_t i = 0;
    for (const auto& s : ss)
    cout << i++ << ": " << s << '\n';
    // ループのあとでも i が使えてしまう
    }
    int main() {
    vector ss = { ... };
    for (size_t i = 0; const auto& s : ss)
    cout << i++ << ": " << s << '\n'; // i のスコープはループ内のみ
    }
    50

    View Slide

  51. 比較演算子の実装を楽にしよう
    15
    51

    View Slide

  52. クラスに 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

    View Slide

  53. C++20 では、三方比較演算子 <=> を定義することで、最大 6 つの比較演算子を自動導
    出できる。とくに、クラスの基底と全メンバを宣言順に辞書式比較するだけのケースで
    は、<=> の実装さえ default でコンパイラに任せられる。
    <=> を default 以外で実装する場合、<, <=, >, >= の 4 つの比較演算子のみが自動
    導出され、== を追加で定義する必要がある。例えば動的配列の同値比較では、最初にサ
    イズだけを比較するという効率的な実装を提供できる可能性があるためである。
    比較演算子の実装を楽にしよう
    struct Date {
    int year, month, day;
    auto operator <=>(const Date&) const = default;
    };
    53

    View Slide

  54. 構造体のメンバ変数を
    名前でわかりやすく初期化しよう
    16
    54

    View Slide

  55. 構造体のメンバ変数の初期化は、どのメンバに何を設定したのかわかりにくい。
    名前を使ってあとから代入すると 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

    View Slide

  56. 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

    View Slide

  57. リテラル演算子を定義して
    定数を簡潔に書こう
    17
    57

    View Slide

  58. リテラル (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

    View Slide

  59. リテラル演算子を定義すると、リテラルに独自のサフィックスを付加でき、サフィック
    スの付いた定数は、その値を引数にした別の関数の呼び出しに置き換えられる。
    イメージ:
    リテラル演算子を定義するコードの例:
    リテラル演算子を定義して定数を簡潔に書こう
    100_mySuffix MyFunc(100)
    "abc"_mySuffix MyFunc("abc", 3) // 第2引数は文字列の長さ
    constexpr double operator""_deg(long double deg) {
    return deg * (PI / 180.0);
    }
    constexpr MyRepeatObj operator""_repeat(const char* s, size_t len) {
    return MyRepeatObj{ s, len };// operator() を持つオブジェクトを返す
    }
    59

    View Slide

  60. 柔軟になったラムダ式の
    キャプチャを活用しよう
    18
    60

    View Slide

  61. C++11 でラムダ式が追加され、関数オブジェクトを手軽に作れるようになった。
    しかし、C++11 時点でのキャプチャにはいくつかの制約があった。
    柔軟になったラムダ式のキャプチャを活用しよう
    struct S {
    vector m_buffer;
    void work(unique_ptr 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

    View Slide

  62. C++14 のラムダ式では、任意の式の結果をキャプチャできるようになり、より少ない制
    約でラムダ式を作れるようになった。
    柔軟になったラムダ式のキャプチャを活用しよう
    struct S {
    vector m_buffer;
    void work(unique_ptr ptr, int n) {
    auto f = [&b = m_buffer, // 新しい参照変数 b でメンバ変数を参照キャプチャ
    p = move(ptr), // 新しい変数 p を作ってムーブキャプチャ
    flag = (n > 3) // 新しい変数 flag で式の結果をキャプチャ
    ]() mutable { ... };
    }
    };
    62

    View Slide

  63. 複数の値は std::pair や std::tuple で返し、
    構造化束縛で受け取ろう
    19
    63

    View Slide

  64. 複数の値は std::pair や std::tuple で返し、
    構造化束縛で受け取ろう
    関数で複数の値を返したい場合、構造体を作るのが手間であれば、代わりに std::pair
    (要素が 2 つ) または std::tuple (要素が 3 つ以上)を使うことができる。C++17 か
    らは pair や tuple の戻り値を { } だけで構築できる。
    pair GetTwo() {
    // return make_pair(35.2, 136.9); // C++14 まで
    return{ 35.2, 136.9 };
    }
    tuple GetThree() {
    // return make_tuple(35.2, 136.9, string("Nagoya")); // C++14 まで
    return{ 35.2, 136.9, "Nagoya" };
    }
    64

    View Slide

  65. 複数の値は std::pair や std::tuple で返し、
    構造化束縛で受け取ろう
    pair や tuple の結果はそのままでは扱いづらいため、構造化束縛宣言により名前を付
    けるとよい。構造化束縛は配列や集成体にも使え、auto& や auto&& で受け取ることも
    できる。
    pair GetTwo();
    tuple GetThree();
    int main() {
    auto[x, y] = GetTwo();
    cout << x << ", " << y << '\n';
    auto[lat, lng, name] = GetThree();
    cout << lat << ", " << lng << ", " << name << '\n';
    }
    65

    View Slide

  66. [[nodiscard]] で
    関数の使い間違いを防ごう
    20
    66

    View Slide

  67. 関数に [[nodiscard]] を付けると、戻り値を誤って無視したときに警告してくれる。
    • 戻り値を無視すると呼ぶ意味がない関数に、積極的に付けてよい
    • 乱数生成関数は? → 内部ステートを更新する副作用あり。戻り値無視も意味がある
    [[nodiscard]] で関数の使い間違いを防ごう
    struct Float2 {
    float x, y;
    [[nodiscard]] Float2 normalized() const; // 正規化した値を返す
    };
    int main() {
    Float2 v(1.0f, 1.0f);
    v.normalized();
    Use(v);
    }
    67
    警告が発生(戻り値を無視)

    View Slide

  68. クラステンプレートの
    テンプレート引数推論を活用しよう
    21
    68

    View Slide

  69. クラステンプレートのテンプレート引数を、コンストラクタの引数の型から推論できる
    ようになり、より短いコードでクラステンプレートを使えるようになった。
    クラステンプレートのテンプレート引数推論を活用しよう
    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);
    }
    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

    View Slide

  70. 無効値を表現したいときに
    std::optional を使おう
    22
    70

    View Slide

  71. std::optional を使うと、型 Type が「無効値」も保持できるようになる。サ
    イズが 0 か 1 の vector と考えるとわかりやすい。有効値を持つかを if (op)
    や op.has_value() で確認し、*op または op.value() で実際の値を取得する。
    無効値を表現したいときに std::optional を使おう
    optional oi; // デフォルトでは無効値
    optional 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

    View Slide

  72. op.value_or(x) は、有効値を保持している場合はその値を、保持していない場合は代
    わりに x を返す。Type と比較可能な型との比較演算も定義される。
    無効値を表現したいときに std::optional を使おう
    optional oi;
    optional 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

    View Slide

  73. 使えそうな場所: ある型のデータがあり、その値が有効か無効かも表現するために、追加
    で 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

    View Slide

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

    View Slide

  75. 型安全な共用体を
    std::variant で実現しよう
    23
    75

    View Slide

  76. C++17 から、union の代替となる std::variant<...Types> が追加。variant の
    変数には、テンプレート引数の型リストに含まれる型のオブジェクトを代入できる。保
    持しているオブジェクトのデストラクタが適切に呼ばれ、正しくない型で読み取ろうと
    した場合には例外を送出するなど、union には無かった型安全性が得られる。
    型安全な共用体を std::variant で実現しよう
    int main() {
    variant v1 = 3.14, v2 = "Hello"s;
    if (v1.index() == 1) // 保持している型を型リストのインデックスで返す
    cout << get<1>(v1) << '\n'; // 3.14
    if (holds_alternative(v2)) // 保持してる型が、ある型に一致するかを返す
    cout << get(v2) << '\n'; // Hello
    v2 = true; // 保持する型を bool 型の値に変更。string オブジェクトは破棄される
    cout << v2.index() << '\n'; // 2
    }
    76

    View Slide

  77. std::visit 関数を使うと、保持してる型に応じて行う処理をシンプルに記述できる。
    型安全な共用体を std::variant で実現しよう
    int main() {
    variant 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

    View Slide

  78. テンプレートの型に応じた処理には
    constexpr if を使おう
    24
    78

    View Slide

  79. 1 つの関数テンプレートの中に、異なる型を想定したコードを記述すると、実体化の際
    にエラーになる。
    テンプレートの型に応じた処理には constexpr if を使おう
    template
    auto GetValue(T t) {
    if (is_pointer_v)
    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

    View Slide

  80. C++17 の constexpr if を使うと、コンパイル時に条件を満たさない部分を実体化の
    対象から除外できる。複数必要だった関数テンプレートの実装を 1 つに簡略化できる。
    テンプレートの型に応じた処理には constexpr if を使おう
    template
    auto GetValue(T t) {
    if constexpr (is_pointer_v) // 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

    View Slide

  81. テンプレートパラメータに
    コンセプトで制約を加えよう
    25
    81

    View Slide

  82. テンプレートの型に制約を加えるには、SFINAE (スフィナエ) と呼ばれる複雑なイディ
    オムを使う必要があった。
    テンプレートパラメータにコンセプトで制約を加えよう
    template >* = nullptr>
    T Mod(T x, T y) { // ↑ T を整数型に制約する
    return x % y;
    }
    template >* = 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

    View Slide

  83. C++20 ではコンセプトという言語機能が追加され、requires 節 / 式によるシンプル
    な文法でテンプレートの型に制約を課せる。条件を満たさなかった時のエラーも「制約
    を満たさない」という読みやすいものに。
    テンプレートパラメータにコンセプトで制約を加えよう
    template requires is_integral_v
    T Mod(T x, T y) {
    return x % y;
    }
    template requires is_floating_point_v
    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
    } 83

    View Slide

  84. 標準のコンセプトがいくつか用意されているので、それを使えばさらに短くなる。
    テンプレートパラメータにコンセプトで制約を加えよう
    template // integral コンセプト
    T Mod(T x, T y) {
    return x % y;
    }
    template // 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

    View Slide

  85. 「このような操作ができる型」という例示によって制約を定義することも可能。
    テンプレートパラメータにコンセプトで制約を加えよう
    template // 同じ型どうしの + 演算が定義されている型
    concept Addable = requires (T x) { x + x; };
    template
    auto Add3(T a, T b, T c) {
    return a + b + c;
    }
    template // .size() メンバ関数を持つ型
    concept HasMemberFuntionSize = requires (T x) { x.size(); };
    template
    void ShowSize(const T& c) {
    cout << c.size() << '\n';
    } 85

    View Slide

  86. カスタマイゼーションポイント
    オブジェクト (CPO) を優先して使おう
    26
    86

    View Slide

  87. カスタマイゼーションポイント
    オブジェクト (CPO) を優先して使おう
    std::swap() や std::begin(), std::end() などの関数テンプレートをユーザ定
    義型に対しても正しく機能させるには、「using 後に非修飾で呼び出す」という、少し
    複雑な使い方をする必要がある (詳しくは『Effective C++ 第3版』25 項参照)。
    template
    void F(Type& a, Type& b) {
    ...
    using std::swap;
    swap(a, b);
    }
    87

    View Slide

  88. カスタマイゼーションポイント
    オブジェクト (CPO) を優先して使おう
    C++20 の標準ライブラリに導入されたカスタマイゼーションポイントオブジェクト
    (CPO) とよばれるグローバルな関数オブジェクトは、内部実装にコンセプトを使うこと
    で同様のことを 1 行で実現してくれる。
    コードの短縮や、ユーザ定義型に対する誤った関数呼び出しを避けるために、C++20 以
    降では、swap(), begin(), end(), size(), ssize() などは、std::ranges 名
    前空間にある CPO を使用すべきである。
    template
    void F(Type& a, Type& b) {
    ...
    std::ranges::swap(a, b);
    }
    88

    View Slide

  89. 文字列を std::string_view で
    受け渡ししよう
    27
    89

    View Slide

  90. 文字列を 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

    View Slide

  91. 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

    View Slide

  92. ポインタのバイト境界を
    コンパイラに伝えよう
    28
    92

    View Slide

  93. データのアドレスが 16 バイト境界にアライメントされていると、コンパイラが SIMD
    を使った高速なコードを生成できる可能性がある。ポインタの指すデータがアライメン
    トされていることをコンパイラに伝える方法として、関数テンプレート
    std::assume_aligned() が用意される。
    実行時に実際に渡されるポインタのアライメントがヒントよりも小さいと未定義動作に
    なるため、慎重に使う必要がある。
    ポインタのバイト境界をコンパイラに伝えよう
    void Multiply(float* x, size_t size, float factor) {
    // 16 バイトアライメントのヒント付きのポインタを返す
    float* ax = assume_aligned<16>(x);
    for (size_t i = 0; i < size; ++i) // ループが最適化される可能性
    ax[i] *= factor;
    }
    93

    View Slide

  94. 配列要素の
    効率的なシフト操作を知ろう
    29
    94

    View Slide

  95. 配列の要素を左右にシフトしたい場合 std::rotate() が使えるが、はみ出た分は回転
    して反対側に移動するため、はみ出た分が不要な場合は余分なコストに。また、
    std::rotate() は要素をどちらにどれだけ移動するか明快なコードで表現できない。
    配列要素の効率的なシフト操作を知ろう
    int main() {
    vector v = { 1, 2, 3, 4, 5 };
    // 左へ 1 ローテートすると { 2, 3, 4, 5, 1 } に
    rotate(v.begin(), v.begin() + 1, v.end());
    // { 2, 3, 4, 5, 6 } に
    v.back() = 6;
    }
    95

    View Slide

  96. C++20 では、範囲の要素を回転無しでシフトさせる std::shift_left(),
    std::shift_right() 関数が追加。移動により要素がなくなった部分(未規定の状
    態)は、新しい要素を代入するか .erase() で削除する。
    配列要素の効率的なシフト操作を知ろう
    int main() {
    vector v = { 1, 2, 3, 4, 5 };
    // 左へ 1 シフトすると { 2, 3, 4, 5, (未規定) } に
    shift_left(v.begin(), v.end(), 1);
    // { 2, 3, 4, 5, 6 } に
    v.back() = 6;
    }
    96

    View Slide

  97. 非順序連想コンテナを
    もっと効率よく使おう
    30
    97

    View Slide

  98. unordered_map では、ルックアップ操作でキーに文字列リテラルを
    使うと一時オブジェクトが作られてしまう。string_view を使うこともできない。
    非順序連想コンテナをもっと効率よく使おう
    int main() {
    unordered_map table = {{ "one", 1 }, { "two", 2 }};
    table.find("three"); // string 型の一時オブジェクトが作られる
    string_view sv = "two";
    table[sv]; // エラー
    table[string(sv)]; // OK
    }
    98

    View Slide

  99. 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; // 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
    ハッシュ関数オブジェクト
    比較関数オブジェクト

    View Slide

  100. C++20 でもっと効率よくする手順 2:
    用意した関数オブジェクトを unorderd_map のテンプレート引数 Hash, Pred に使う。
    非順序連想コンテナをもっと効率よく使おう
    struct string_compare { /* 前ページ */ };
    struct string_hash { /* 前ページ */ };
    int main() {
    unordered_map table =
    {{ "one", 1 }, { "two", 2 }};
    table.find("three"); // string 型の一時オブジェクトは作られない
    string_view sv = "two";
    table[sv]; // OK
    }
    100
    ここに

    View Slide

  101. ステートレスなメンバ変数の
    メモリ消費をゼロにしよう
    31
    101

    View Slide

  102. アロケータなどをクラスのメンバ変数として保持するとき、それがステートレスな(メ
    ンバ関数のみでメンバ変数が無い)クラスであっても、オブジェクトにはアドレスを一
    意に用意するという C++ の規則のせいで、サイズを 0 にできずメモリ消費が余分に増
    えてしまう問題があった。
    ステートレスなメンバ変数のメモリ消費をゼロにしよう
    struct Empty {}; // ステートレスなクラス
    struct X {
    Empty e; // 中身がないくせに自分専用のアドレスを確保する
    int32_t i;
    };
    sizeof(int32_t) より大きい
    102

    View Slide

  103. 「空の基底クラスは最適化によってサイズ 0 にしてよい」という仕様を利用して、この
    問題を回避する Empty Base Optimization (EBO) というテクニックが std::tuple や
    std::unique_ptr の内部実装などでも使われているが、複雑なクラスでは継承が増え
    てコードの見通しが悪くなる問題があった。
    ステートレスなメンバ変数のメモリ消費をゼロにしよう
    struct Empty {}; // ステートレスなクラス
    struct X : Empty { // 空の基底クラスは専用のアドレスが不要
    int32_t i;
    };
    sizeof(int32_t) になる
    103

    View Slide

  104. C++20 ではステートレスなメンバ変数の宣言に [[no_unique_address]] 属性を付
    けることで、専用のアドレスの確保を回避して余分なメモリ消費を節約できる。継承を
    使わないシンプルなコードで EBO と同じ効果を得られる。
    ステートレスなメンバ変数のメモリ消費をゼロにしよう
    struct Empty {}; // ステートレスなクラス
    struct X {
    [[no_unique_address]] Empty e; // アドレスは i と共用
    int32_t i;
    };
    sizeof(int32_t) になる
    104

    View Slide

  105. 分岐の起こりやすさを
    コンパイラに伝える方法を知ろう
    32
    105

    View Slide

  106. C++20 では、if や switch-case について、起こりやすい / 起こりにくい分岐を
    [[likely]], [[unlikely]] 属性でコンパイラに伝えられる。コンパイラはその情
    報をヒントに命令の並び替えなどを行い、高速に実行されるコードを生成できる。GCC
    に以前から存在した __builtin_expect() のような機能を標準化したもの。
    どこでも無条件で最適なコードが生成される魔法の機能ではなく、逆に速度が低下する
    ような場合もあるため、プロファイリングを注意深く行う必要がある。
    分岐の起こりやすさをコンパイラに伝える方法を知ろう
    void Draw(const vector& v) {
    if (v.empty()) [[unlikely]] // 起こりにくいというヒントを伝える
    return;
    m_context->draw(v.data(), v.size());
    }
    106

    View Slide

  107. 数値 → 文字列の変換には
    std::to_chars を使おう
    33
    107

    View Slide

  108. 数値から文字列への変換は基本的な機能である。しかし C++標準ライブラリの関数には、
    ロケール考慮やメモリの動的確保、例外送出、(sprintf のような) 書式の解析など、余
    分なコストが発生する関数しか存在しなかった。C++17 の std::to_chars() は、そ
    れらの要素をすべて排除した最速の変換を提供する。文字列の格納先はあらかじめ用意
    し、出力フォーマットは引数で指定する。
    数値 → 文字列の変換には std::to_chars を使おう
    int main() {
    double x = 3.141593;
    char buf[numeric_limits::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

    View Slide

  109. 文字列 → 数値の変換には
    std::from_chars を使おう
    34
    109

    View Slide

  110. 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

    View Slide

  111. 洗練された
    文字列フォーマット機能を使おう
    35
    111

    View Slide

  112. 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

    View Slide

  113. std::formatter クラスを特殊化することで、自作のクラスも format の中で使える
    ようになる。
    洗練された文字列フォーマット機能を使おう
    enum class Color { Red, Green, Blue };
    constexpr string_view names[] = { "Red", "Green", "Blue" };
    template<>
    struct formatter : formatter {
    auto format(Color c, format_context& ctx) {
    return formatter::format(names[static_cast(c)], ctx);
    }
    };
    int main() {
    cout << format("{} Ocean\n", Color::Blue); // Blue Ocean
    }
    113
    formatter 特殊化
    Color 型を format で使える

    View Slide

  114. 次のようなリテラル演算子 _fmt を定義してあげると、さらにコンパクトに記述できる。
    洗練された文字列フォーマット機能を使おう
    struct format_string {
    string_view fmt;
    template
    auto operator()(T&& ...args) const {
    return format(fmt, forward(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 リテラル演算子の定義

    View Slide

  115. コンパイル時に実行できる
    標準ライブラリ関数の操作を知ろう
    36
    115

    View Slide

  116. コンパイル時に実行できる
    標準ライブラリ関数の操作を知ろう
    標準ライブラリのアルゴリズム関数の多くが constexpr に対応し、要素の検索やソー
    トなどの操作をコンパイル時に実行できるようになった。リテラル文字列を区切り文字
    で分割したり、数列を昇順にするなどの処理をコンパイル時に移し、実行時の処理を減
    らすような API 設計ができるようになる。
    int main() {
    // コンパイル時にファイル名と拡張子に分割するような関数
    constexpr auto [filename, extension] = Split("aaa.txt");
    // コンパイル時にソートするような関数
    constexpr auto sorted = Sort({ 15, 30, 10, 5 });
    }
    116

    View Slide

  117. コンパイル時と実行時で
    処理を分ける方法を知ろう
    37
    117

    View Slide

  118. コンパイル時に数学関数の結果を得たいとき、遅いが 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

    View Slide

  119. 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

    View Slide

  120. 新しいアロケータを知ろう
    38
    120

    View Slide

  121. 従来のコンテナは、アロケータもコンテナの型情報に含むため、異なるアロケータを使
    う同種のコンテナは異なる型になり、データのやり取りが面倒だった。
    新しいアロケータを知ろう
    void F(const vector& v) {}
    int main() {
    vector v1 = { 1, 2, 3 };
    // コンパイルエラー:型が一致しない
    vector> v2 = v1;
    // こうすれば OK だが面倒
    vector> v3(v1.begin(), v1.end());
    // コンパイルエラー
    F(v3);
    }
    121
    vector と
    vector> は別

    View Slide

  122. C++17 では、ポリモーフィックなアロケータ(多相アロケータ)を利用することで、ア
    ロケータの詳細を型情報に含まない各種標準コンテナが std::pmr 名前空間に提供され
    る (std::pmr::vector など)。
    実際のメモリ確保・解放は std::pmr::memory_resource インタフェースに実装する。
    新しいアロケータを知ろう
    void F(const pmr::vector& v) {}
    int main() {
    pmr::vector v1; // デフォルトの memory_resource を使用
    MyMemoryResource myMR; // ユーザが実装した memory_resource を使用
    pmr::vector v2{ v1, &myMR }; // v1 も v2 も同じ型
    F(v1); // OK
    F(v2); // OK
    }
    122
    メモリ確保・解放処理はポインタで渡す
    v1 も v2 も pmr::vector

    View Slide

  123. 標準の memory_resource の 1 つ、std::pmr::monotonic_buffer_resource は、
    そのメモリリソースを使って確保したすべてのオブジェクトのメモリを、そのメモリリ
    ソースのデストラクタまで解放しないことで、メモリ解放の頻度を減らす。
    新しいアロケータを知ろう
    int main() {
    pmr::monotonic_buffer_resource mono;
    {
    pmr::vector v1{ { 1,2,3,4 }, &mono };
    } // ここでは v1 のメモリを解放しない
    {
    pmr::vector v2{ { 5,6,7,8 }, &mono };
    } // ここでは v2 のメモリを解放しない
    } // ここで v1, v2 のメモリを解放
    123

    View Slide

  124. ヘッダに代わるファイル分割の仕組み
    「モジュール」
    39
    124

    View Slide

  125. C++20 から、ファイルの内容を展開するだけの #include に代わる新しいファイル分
    割の仕組みとしてモジュールが導入された。
    モジュールではマクロ定義がそれぞれのモジュールの範囲を越えないため、import 順に
    よる問題が生じない。コンパイルはおおむね速くなるとされているが、現在の実装では
    並列数が多い環境で逆に遅くなるケースも報告されていて、コンパイラの改良に期待で
    ある。C++ 標準ライブラリのモジュール化も C++23 以降に予定されている。
    ヘッダに代わるファイル分割の仕組み「モジュール」
    myLib.cpp main.cpp
    // モジュール宣言
    export module myLib;
    // 関数をエクスポート
    export void MyFunc() { ... }
    import myLib;// myLib をインポート
    int main() {
    MyFunc(); // インポートした関数を呼ぶ
    }
    125

    View Slide

  126. 中断と再開をサポートする関数
    「コルーチン」
    40
    126

    View Slide

  127. 呼び出した処理の途中で中断・再開ができるような関数、コルーチンが C++ に導入さ
    れる。C++20 では、コルーチンライブラリを作るのに必要最低限の言語機能とヘッダが
    導入された。アプリケーション開発でコルーチンを十分に活用するには、現時点では非
    標準のコルーチンライブラリ (CppCoro など) を使用するか、C++23 もしくは C++26
    で予定されている標準ライブラリによるコルーチンサポートを待つ必要がある。
    中断と再開をサポートする関数「コルーチン」
    generator 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

    View Slide

  128. assert の進化版
    「契約プログラミング」
    41
    128

    View Slide

  129. 関数の引数や戻り値について、assert のようなチェックを言語機能として行う契約プ
    ログラミングが C++23 で計画されている。 [[expects]] 属性に事前条件、
    [[ensures]] 属性に事後条件を宣言。契約違反のハンドラーはカスタマイズ可能。デ
    フォルトの動作は規定されないが、従来の assert と同じにする処理系が多いだろう。
    関数の満たすべき条件をコードに明示的に書き、実行時にチェックしたり、静的解析で
    活用したりすることで、より安全なプログラミングが可能になる。とくに事後条件は
    assert では書きにくかった。コンパイラが最適化のヒントとする応用も考えられる。
    assert の進化版「契約プログラミング」
    double SqrtChecked(double x)
    [[expects: x >= 0]] // 引数に対する事前条件
    [[ensures r: r >= 0]] // 戻り値に対する事後条件
    {
    return std::sqrt(x);
    }
    129

    View Slide

  130. より柔軟な条件分岐
    「パターンマッチング」
    42
    130

    View Slide

  131. 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

    View Slide

  132. タプル (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

    View Slide

  133. まだまだある、
    C++11~C++20 注目の機能
    ++
    133

    View Slide

  134. まだまだある、C++11~C++20 注目の機能
    • スレッドサポート
    • ジェネリックラムダ
    • インライン変数
    • 最大公約数を求める std::gcd(), 最小公倍数を求める std::lcm()
    • 配列の一部の View をつくる std::span
    • 任意の型を保持できる std::any クラス
    • インクルードファイルが存在するか調べる __has_include()
    • あるコア言語 / ライブラリ機能がコンパイラでサポートされているかを判定する機
    能テストマクロ
    詳しくは cpprefjp の解説ページを: cpprefjp.github.io/lang.html
    134

    View Slide

  135. ① ゲーム開発に役立つ C++11 ~ C++20 の機能
    • 便利な機能
    • コードの書き方が変わる機能
    • 実行時性能が向上する機能
    • 今後に期待な機能
    ② C++ の仕様はどのように決まる?
    • ゲーム開発と C++ 標準化
    • C++ に Issue を送ってみた体験談
    • 国内 WG 委員インタビュー
    • ゲーム開発に関連する、議論が進行中の提案
    • 最新の C++ 情報にキャッチアップ
    135

    View Slide

  136. ゲーム開発と C++ 標準化
    ◆ ゲームを開発しやすい言語にするための標準化
    ゲームは C++ を活用する主要なジャンルの 1 つ。標準化(仕様策定)に参画して発言
    することで、自分たちにとって使いやすいプログラミング言語にしていくことができる。
    ◆ C++ の仕様とゲーム開発
    ゲーム開発に関係する C++ の言語や標準ライブラリ機能 (ネットワーク、グラフィッ
    クス、オーディオ等) が他国主導で決められると、日本のゲームハードウェアの性能を
    生かしきれない場合もあり、独自 API 乱立でゲーム開発のハードルが上がる原因に。
    C++ の新しい
    標準機能です!
    このハードで使うと問題が
    あるので使用禁止です。
    136

    View Slide

  137. C++ は国際標準規格
    国際標準規格は、一企業ではなく様々な企業や国の代表者が話し合いで規格を決める。
    分野によっては特許収入などの利害も絡み競争的なものもあるが、C++ の場合は緩い。
    仕様を決める過程がオープンで気軽に参加でき、欲しい機能を追加するチャンスが誰に
    でもある。C++ ではオープンな議論の結果を代表者 (ISO) が追認するという形式。
    コミュニティ 標準化委員会
    ISO 国際標準
    コンパイラ・
    標準ライブラリ
    開発者
    提案
    コメント
    規格案
    技術報告 規格書 実装
    137

    View Slide

  138. 近年発行された C++ の規格
    C++98 初の国際標準化。正式名は ISO/IEC 14882:1998
    C++03 C++03 からのマイナーな改訂
    C++11 範囲ベース for, ラムダ式, スマートポインタ, , など
    C++14 2 進数リテラル、変数テンプレート、[[deprecated]] 属性など
    C++17 構造化束縛、[[nodiscard]] 属性、, など
    C++20 一貫比較、コンセプト、モジュール、, など
    C++23 (予定)
    標準ライブラリのモジュール対応、コルーチンのライブラリ対応、ネット
    ワークなど(予定)
    8 年
    3 年
    ごと
    138

    View Slide

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

    View Slide

  140. C++ の規格書と規格ドラフト
    ◆ 規格書
    C++14, C++17 など、発行済みの C++ 国際標準規格の仕
    様書。C++17 では 1600 ページ超。公式版は約 2 万円。
    ◆ 規格ドラフト
    規格書の一歩手前、標準化委員会による規格ドラフト(レ
    イアウトなどの調整が未適用、内容はほぼ同等)は無料で
    閲覧できる。C++20 の規格ドラフトは wg21.link/n4861
    Q: これを読めば C++ を勉強できる?
    A: ノー。規格書はコンパイラ開発者や標準ライブラリ作者など専門家向けに書かれ
    ている仕様書。入門書のような解説は含まれていない
    140

    View Slide

  141. C++ に提案を送る 2 つの方法
    ◆ 番号付き文書 (Papers):
    コア言語や標準ライブラリの機能を新しく追加・変更する提案や、既存の提案に対する
    フィードバックを格式ばった書式でまとめた文書。作法があるため、専門家の指導やア
    ドバイスを受けながら書く。採用されるためには、提案を出したあとも標準化会議での
    発表や提案文書の改訂など数か月~数年単位で関与する必要がある。
    これまでの番号付き文書一覧: www.open-std.org/jtc1/sc22/wg21/docs/papers/
    ◆ Issue / Defect Report:
    コア言語・標準ライブラリの規格のバグは Issue または Defect Report (欠陥報告) と
    して投稿する。件数としては番号付き文書の数倍以上が提出される。ほとんどの場合
    メーリングリスト上で完結するため、比較的低コストで C++ に貢献できる。
    141

    View Slide

  142. 提案が規格になるまで
    出典: https://isocpp.org/std/the-committee
    何かを思いつく
    EWG / LEWG: 設計を妥当なものにする
    CWG / LWG: 仕様文言を洗練させる
    規格ドラフトにマージ
    国際標準規格として発行 (3 年ごと)
    番号付き文書の提出・会議での発表
    フォーラム、メーリングリスト等での議論
    専門家の集まるフォーラムに投稿
    承認
    承認
    承認
    C++ 標準化委員会
    142

    View Slide

  143. C++ の提案文書を読んでみよう 1/3
    P0769R2
    Date: -----
    Author: -----
    Audience: LWG
    Add shift to
    I. Introduction
    This paper proposes adding shift algorithms to the C++ STL which
    move elements forward or backward in a range of elements.
    提案のタイトル
    文書番号 + リビジョン番号
    提出日
    著者
    議論を行うワーキンググループ名
    概要の説明
    143

    View Slide

  144. 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
    .
    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

    View Slide

  145. VI. Proposed Wording
    - In [algorithm.syn], after the declaration of shuffle, add:
    // [], shift
    template
    constexpr ForwardIterator shift_left(
    ForwardIterator first, ForwardIterator last,
    typename iterator_traits::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

    View Slide

  146. C++ に Issue を送ってみた体験談 1/4
    ◆ 規格ドラフトに怪しいところを発見
    cpprefjp の記事執筆中、C++20 の規格ドラフトの中に誤りと思われる箇所を発見。
    std::pmr::polymorphic_allocator という標準ライブラリのクラスの 3 つの関数
    に [[nodiscard]] が付いていなかった。
    ◆ 提案文書を調べた
    関連する採択済み文書 P0339R6 と P0600R1 を調べると、前者には当該関数を追加す
    る提案、後者にはその関数を含む様々な関数に [[nodiscard]] を付ける提案があった。
    したがって、これらの提案文書をドラフトにマージする際、誤って見落とされたと判断。
    ◆ Issue として報告しようと決めた
    関数への [[nodiscard]] の付け忘れは、過去の Issue にも報告があった。今回見つけ
    た問題についても Issue を投稿してみることに。
    146

    View Slide

  147. 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
    [[nodiscard]] 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 [[nodiscard]] T* allocate_object(size_t n = 1);
    template void deallocate_object(T* p, size_t n = 1);
    template [[nodiscard]] T* new_object(CtorArgs&&... ctor_args);
    Change 20.12.3.2 Member functions [mem.poly.allocator.mem]
    メールを送る前にフォーラムで相談し
    たほうが良い。
    isocpp.org/std/submit-issue
    147

    View Slide

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

    View Slide

  149. C++ に Issue を送ってみた体験談 4/4
    ◆ C++20 規格ドラフトに反映!
    2019 年 10 月の Issue 投稿から約 4 か月後、2020 年 2 月の C++ 標準化国際会議で
    承認され、ステータスが WP (最新のドラフトへ反映)に。
    ◆ (余談)アメリカの C++ 標準化 WG よりも早かった
    2019 年 11 月にアメリカの C++ 標準化 WG も同じ問題の一部を報告したが、自分の
    報告が先行していて上位互換だったため、自分の Issue が採択された。
    149

    View Slide

  150. C++ 標準化に日本から関わっている人たち
    ~ 国内 WG 委員へのインタビュー ~
    答えてくれた方
    2 カ月に一度ぐらい、メンバーの十数人が集まって、各自が興味を持った
    新しい提案の解説をして問題点などを議論しています。
    メーリングリストや委員会での議論を通して、提案やコメントを出すこと
    もあります。
    SC22/C++WG 小委員会委員
    光成 滋生 さん (サイボウズ・ラボ株式会社)
    Twitter @herumi e-mail herumi nifty.com
    Q1. 国内 WG ではどのような活動を?
    150

    View Slide

  151. 法人として、ITSCJ 規格賛助員 / 準賛助員に加入する必要があります。
    詳細は「ITSCJ > よくあるご質問」ページ
    www.itscj.ipsj.or.jp/faq/index.html
    をご覧ください。
    Q2. 国内 WG にはどうすれば参加できますか?
    私は 2008 年頃から C++11 の規格を決めるためにアドバイザ(ボラン
    ティア)として参加を始めました。
    詳しい知識を持つ方たちと直接議論できるのは大変勉強になります。
    ただし、Q2 で答えたように現在は基本的に法人として参加しなければな
    らないので 2015 年からは業務の一環として活動しています。
    Q3. ボランティアなのですか?
    151

    View Slide

  152. 公式には「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

    View Slide

  153. ゲーム開発に関連する、議論が進行中の提案
    ◆ 2D グラフィックス
    簡易的な 2D 描画機能を標準ライブラリにオプションで組み込む
    ◆ オーディオ
    音声の波形データの再生機能を標準ライブラリに追加
    ◆ 半精度浮動小数点数
    画像処理や機械学習で使われる 16-bit 浮動小数点数型のネイティブサポート
    ◆ flat_set, flat_map
    ソート済みの配列を使った、空間効率のよい連想コンテナ
    ◆ 統計ライブラリ
    平均や標準偏差など、初歩的な統計処理を行う標準ライブラリ機能
    153

    View Slide

  154. 最新の 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

    View Slide

  155. 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

    View Slide

  156. 謝辞
    ◆ 機能解説 監修
    高橋 晶 さん (株式会社 Preferred Networks / cpprefjp)
    川崎 洋平 さん (cpprefjp)
    ◆ C++ 標準化 取材協力・監修
    光成 滋生 さん (サイボウズ・ラボ株式会社 / SC22/C++WG 小委員会委員)
    ◆ Thanks
    cpprefjp コントリビュータの皆様、cppmap コントリビュータ・スポンサーの皆様
    講演リハーサルのチェックをしてくださった皆様
    (サンプルコードの一部は cpprefjp / cppmap の記事を引用しています)
    156

    View Slide

  157. おまけ
    ◆ PowerSyntax
    PowerPoint でコードをシンタックスハイライトするアドイン
    github.com/tetsurom/PowerSyntax
    #include
    int main()
    {
    std::cout << "Hello, C++!\n";
    }
    #include
    int main()
    {
    std::cout << "Hello, C++!\n";
    }

    View Slide

  158. ゲーム開発者のための 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

    View Slide

  159. 質問 / 事前に寄せられた質問 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

    View Slide

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

    View Slide

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

    View Slide

  162. 質問 / 事前に寄せられた質問 4
    Q: std::is_constant_evaluated() の登場により、数学関数が全て constexpr 化
    できそうですが、今後そのようになることはあるでしょうか?
    A: GCC や Clang はコンパイル時に計算できる数学関数を拡張機能として提供していま
    す。C++ 標準でもそれらを参考に数学関数の一部を constexpr 対応させようという提
    案があります (P0533R6: [Library] constexpr for and ) 。実行時の
    浮動小数点数の丸めモードの影響をどうするかなど、課題がまだあります。
    162

    View Slide

  163. 質問 / 事前に寄せられた質問 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/

    View Slide

  164. 質問 / 事前に寄せられた質問 6
    Q: 構造化束縛は便利ですが、tuple を使うことによるビルド速度への影響はどうでしょ
    うか。
    A: 複雑なテンプレートを実体化する tuple はビルド時間に影響します。しかし、
    C++20 では [[no_unique_address]] や、条件付き explicit など、標準の tuple
    の実装を従来よりも簡潔にする言語機能が入ったため、少しは改善することが期待され
    ます。コンパイル時間が気になる場合や、メンバに名前を付けておきたい場合は、構造
    体を返すのがベターです。
    164
    参考: cor3ntin.github.io/posts/tuple/

    View Slide

  165. 質問 / 事前に寄せられた質問 7
    Q: 浮動小数点数は、コンパイルごとに式の最適化が任意だと聞いています。これにより
    ゲームの計算結果が異なるため、通信対戦やリプレイ再生に困ります。これを統一する
    オプションの構想はでていたりするのでしょうか。
    A: 同一バイナリを配布したとしても、クライアントごとに計算結果が異なることはさま
    ざまな原因からありえるため、これはプログラムの設計の範疇になると思われます。浮
    動小数点数について C++ が保証する範囲は限定的です。これを変更するような提案は現
    時点では出ていません。
    165
    参考:
    www.jpcert.or.jp/sc-rules/c-flp00-c.html
    christian-seiler.de/projekte/fpmath/

    View Slide

  166. 質問 / 事前に寄せられた質問 8
    Q: C++20 で、まだリリースされるか決まっていない機能はありますか?
    A: C++20 は 2020 年 9 月時点で国際標準ドラフトが承認されていて、あとは ISO の承
    認を残すだけなので、新たに仕様が変更されることはありません。
    166

    View Slide

  167. 質問 / 事前に寄せられた質問 9
    Q: std::size は size_t で返しますが,描画 API の多くは std::uint32_t で受け
    取る関係で警告が出てしまいます。std::size() のような形は無いので
    しょうか?
    A: 残念ながらありません。static_cast を使ってください。
    167

    View Slide

  168. 質問 / 事前に寄せられた質問 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

    View Slide

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

    View Slide

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

    View Slide