CppCon 2026の発表概要まとめ

CppCon 2026が終了し、発表資料が公開されました。YouTubeの方はそのうちアップロードされると思います。

github.com

www.youtube.com

主に自分用にClaude Codeにすべての発表資料を要約させたものを、ここで公開します。興味がもてそうな発表を見つけたらくわしく見る、という用途に使います。CppConのWebサイトに載っている発表概要を翻訳したものではなく、発表資料内の要点をまとめたものです。

Herb Sutterの基調講演だけまだ公開されていないようなので、あとで追記するかもです。

【1. C++26 と標準化】

Profiles for Simplicity and Guarantees — Bjarne Stroustrup

スライド

「安全性・優雅さ・効率のすべてが必要だ。優れたプログラマならモダン C++ で書ける。だが 1,840 万人の開発者全員が常に高品質を出せるわけではない」という問題意識から出発する。

Profile とは「静的解析と実行時チェックの組み合わせによって強制される、望ましい性質の一貫した保証の集合」—たとえば「すべてのオブジェクトは使用前に初期化される」「ダングリングポインタ経由のアクセスは存在しない」。違反時の挙動は profile が規定し、強制されることが保証される。意味論は変えない(従来 UB だったものに意味を与える点を除く)。

GCC・Clang の両方に実装が進んでおり(P3704、P3446 ダングリング除去、P4222 初期化 profile)、「Profiles は C++ の使われ方・教えられ方・見られ方を変える」というのが結論。

C++26: A Curated Tour of What's New — Marc Gregoire

スライド

2026 年 3 月に技術作業が完了した C++26 の全体ツアー。Herb Sutter 評は「C++11 以来もっとも魅力的なリリース」。

トップ 4 機能は静的リフレクション、Contracts、実行制御ライブラリ(std::execution)、そして UB の削減 —C++26 として再コンパイルするだけでコードはよりメモリ安全になる、という点が強調される。コア言語側は _ プレースホルダ、= delete("reason")、構造化束縛の拡張、パラメータパックへの添字アクセス、#embed、constexpr 例外など、ライブラリ側は std::inplace_vector、std::hive、飽和演算、<linalg>、views::concat、std::simd、std::function_ref など。

膨大な小改良(trivial relocatability、<rcu>、<hazard_pointer>、標準ライブラリの堅牢化(hardening)など)まで一望できるのが本発表の価値。

How C++26 Changes the Way We Write Code — Timur Doumler

スライド

Coroutines、Concepts、Ranges、Modules、リフレクション、Contracts、std::execution、std::simd に共通するのは、「意図をより直接的に表現するための抽象」であり、長年の設計変遷と論争を経ており、C++29 以降への拡張が前提になっている、という点。

そこで機能を覚えるのではなく理解するための問いを立てる —「この機能は何の抽象か」「抽象の境界はどこか」「現実世界のアナロジーは何か」。たとえば std::simd は「SIMD レジスタを可搬に操作する抽象」ではなく「データ並列性を可搬に表現する抽象」と捉え直される。

後半はリフレクションの実例(to_json、配列構造体から構造体配列への変換、test_ で始まる関数を全実行するテストランナー、アノテーションによるスキップ)で、C++26 が書き方そのものをどう変えるかを具体的に示す。

The Real Story of C++26: Beyond the Headline Features — Sandor Dargo

スライド

冒頭に置かれるのは const int& f() { return 42; } の 1 行で、今日は運がよければ警告付きで通るこのコードが、C++26 ではコンパイルを通らなくなる。リフレクションも Contracts も関係なく、ただバグが 1 つ存在しなくなる。「大きな機能は何ができるかを変え、小さな機能は何が実用的かを変える」という立場で 5 つのテーマを追う。

正しさの再定義では、ダングリング参照の返却(P2748R5)や不完全型の delete(P3144R2)が不適格(ill-formed)になり、未定義動作と規定された動作の中間としてエラー性動作(erroneous behavior)が導入される。未初期化値の読み出しは誤りのままだが、被害の範囲は限定される。ライブラリの堅牢化(hardening、P3471R4)はメモリ安全性に関わる事前条件だけに的を絞り、Google が数億行に適用した結果は 1,000 件超のバグ検出、セグメンテーション違反 30% 減、オーバーヘッド平均 0.30% だった。

ほかに constexpr な throw(実行時とコンパイル時で同じ関数を使える)、std::function_ref、std::copyable_function(const をシグネチャの一部として扱い、std::function の const バグを避ける)、パラメータパックへの添字アクセスなど。結論は「これは機能一覧ではなく方向である — 正しい C++ を書きやすく、正しくない C++ を捕まえやすくする」。

Towards a Complete Contract-Assertion Facility for C++ — Timur Doumler

スライド

契約を「プログラムが正しいとみなされる条件」、契約違反を「ユーザーのエラーではなくプログラマのバグ」と定義するところから始める。

C++26 の Contracts は「最小限の実用製品(MVP)」であり、基盤は堅牢だが大規模に使うには不足しているとして、C++29 に向けた 6 つの拡張(P3850)を解説する: 仮想関数への pre/post、ユーザー定義エラーメッセージ、明示的な契約違反の発火、事後条件のキャプチャ、表明制御オブジェクト(ラベル)、暗黙の契約表明。

最後の「暗黙の契約表明」が要で、配列の範囲外アクセスのような UB を pre(i < N) として概念的に定義できれば、ignore は通常のコンパイラ、enforce は AddressSanitizer、quick-enforce は -fbounds-safety に対応づく(P3100)。狙いは新技術の発明ではなく、既存のサニタイザやコンパイラフラグを標準の語彙と違反ハンドラの下に統合すること。

IEEE 754 Decimals for C++: The Boost.Decimal Library — Matt Borland

スライド

0.1 + 0.2 が 0.30000000000000004 になるのは精度ではなく表現可能性の問題である(5 は 2 を割り切らない)、だから「もっと精度を」は解にならない、という導入。

そこで金融・人間が入力したデータ・法規制対応のために IEEE 754 の 10 進浮動小数点を C++ に持ち込むのが Boost.Decimal。ヘッダオンリー・依存なし・C++14・全面 constexpr・CUDA 対応で、<cmath> <charconv> <format> まで完備し、x86_64/x86_32/ARM64/ARM32/s390x で検証されている。

実利用も挙げられる。Satoshi(1 億分の 1 BTC)を自前の固定小数点で表現できなかった取引会社、ARM で decimal128 が必要だったデータベース会社、Boost.MySQL の DECIMAL 型など。結論は「データが 10 進なら型も 10 進であるべき。演算にはコストがあるが、パースとシリアライズでは速くなりうる」。

The State of Boost: Boost Is Where You Become the Engineer You Want to Be — Harold Bott

スライド

C++ Alliance CEO による Boost の現況報告。3 幕構成(作り方 / 実績 / 今後)。

中心にある主張は「レビューからは、ドキュメントからは分からないことが分かる」という点にある。ドキュメントから分かるのはライブラリが何をするかであり、レビューから分かるのはなぜその形なのか(試して捨てた 3 つの設計、回避を強いたコンパイラバグ、著者自身が今も疑っている判断)である。そして後者は、本からも、ウェブ検索からも、すべてを読んで何も作ったことのないモデルからも得られない。

shared_ptr / optional / variant / filesystem など標準入りした実績と、約 19,000 の公開リポジトリでの利用を示しつつ、Peter Dimov・Howard Hinnant・Beman Dawes ら担い手の名を挙げて「次に誰が継ぐのか」を問う。答えとして掲げるのは、新人が 5 秒で見切りをつけないよう入口を作り直すこと、ドキュメンタリー、そしてインターンとメンタリングであり、締めは「Boost は、なりたい技術者になる場所だ」。


リフレクション(C++26 静的リフレクション)

What can C++26 reflection do for you? — Andy Webber

スライド

「C++ をネットワークプロトコルの技術仕様そのものにする」ことを目標に、板情報プロトコルのパーサをリフレクションで丸ごと生成した実験レポート。

#pragma pack した構造体をワイヤ形式の定義として扱い、メッセージ構造体を「形」から発見し、ワイヤ上のレイアウトとメモリ上のモデルを分離し、アノテーションで構造体にポリシーを載せる(gcc 16.1 で検証)。

結論は両面ある — リフレクションは柔軟で、アノテーションは表現力があり、宣言的なコンパイル時 DSL をいくらでも作れる。一方でコンパイル時プログラミングの制約(構造型、推移的なコンパイル時確保のみ、即時関数)とそしてデバッグの難しさも同時に述べる。

A Duck Does More Than Quack: Synthesizing VTables with C++26 Reflection — Ryan Keane

スライド

共通基底を持たない複数の型を実行時に差し替えたいとき、継承は侵入的、std::function は operator() のみ、std::any にはインタフェースがなく、既存の型消去ライブラリはマクロが多い。いずれも決め手を欠く。

C++26 リフレクションによる rjk::duck は非侵入的・ゼロボイラープレート・同等性能を同時に達成する。define_aggregate で仮想関数テーブルを生成し、関数子を使って生成集成体から継承し、インタフェースメンバあたり 16 バイト → 8 バイト → ポインタ相互変換可能性と [[no_unique_address]] によって 0 バイトへと詰めていく。最終的にアセンブリは手書きの型消去と同じ 2 ロード、間接参照を 1 つ削れば 1 ロードになる。

著者は前年秋に C++ を学び始めた学部 2 年生で、朝ブログを書いたら午後に CppCon へ、2 時間後には WG21 提案へ招かれたという経緯も語られる。

Can I memcpy This Type Across a Boundary? — Fanchen Su

スライド

共有メモリ IPC、プラグイン/ホスト、スナップショット保存で「オブジェクトのバイト列を境界越しに渡せるか」をC++26 リフレクションでコンパイル時に検証する TypeLayout の発表。

鍵は独立した 2 つの検査が必要だということ — バイトコピー検査(この型はこのコピー方法の要件を満たすか)とレイアウト検査(このビルド間でレイアウトは一致するか)。is_trivially_copyable_v が true でもポインタを含めば失格であり、同じ long double が Linux x86-64 で 16 バイト、ARM64 macOS で 8 バイトという実例が両者の独立性を示す。ポインタは relative_ptr で領域先頭からのオフセットに置き換える。

結論は慎重で、6 ビルドでの検査通過は「この型・このビルド・このコピー方法についての承認」でしかなく、値の意味の妥当性・実データの検証・ストレージと寿命と同期の提供は依然としてアプリケーションの責任である、と明示する。

Zero Cost Scripting Languages for Game Engines With C++26 Static Reflection — Koen Samyn

スライド

「C++ を他言語のコンパイルホストにする」というビジョンのもと、AngelScript のバイトコードをC++26 リフレクションでコンパイル時に C++ テンプレート特殊化へ変換し、VM のオーバーヘッドをコンパイラに消させる試み。

技術の核は std::meta::substitute による特殊化の生成と、畳み込み式による命令呼び出しの合成。バイトコードは #embed で取り込み、NTTP として渡す。

10 回ループして加算する関数は mov eax,0x2d 1 命令に畳み込まれる(VM のディスパッチが完全に消える)。要点は 3 つ — 手法は成立する、生成コードはコンパイラがインライン化して最適化できる、そしてこの技法は他言語にも再利用できる。

The Address is Not The Place: Object Residency in C++26 — Laurie Kirk

スライド

L1/L2/L3 キャッシュ、仮想メモリ、AutoNUMA、CXL が与えてきた「アドレス=場所」という幻想は、NUMA・HBM・階層化メモリ(200ns と 2ms が同居する世界)では破綻する。

オブジェクトについて「誰が(所有権、スマートポインタ)」「いつ(ライフタイム、RAII)」を表す語彙はあるのに「どこに」がない、という指摘から wherency(配置に関わる事実を表明し、ポリシーが適切な常駐先を決められるようにする意味的性質)を造語する。Newton 的な「絶対で不動の場所」から Leibniz 的な「関係としての場所」への転換が主題。

実装としては階層対応のカスタムアロケータ hotpotato(Tier 0 ローカル〜 Tier 4 CXL、LRU による昇格・降格)を示し、コンパイル時の wherency は C++26 のリフレクションとアノテーション(P3394)で型に付与できる、と展望する。


【2. 並行・非同期・Sender/Receiver】

Concurrency for Modern CPUs — Lock-Free or Lock-based? — Fedor Pikus

スライド

CppCon 2017 で「もっとも要求の厳しい並行コードはロックフリーで書け」と述べた本人が、10 年後に「その結論はほぼ 100% 間違っていた」と自己修正する発表。機械も基礎も変わっていないのに何が変わったのかを問う。

共有整数のインクリメントを Granite Rapids・Apple M3 Pro・NVIDIA Grace で測ると、高競合下ではスピンロックがアトミック操作を上回り、lock-free(CAS)は wait-free より遅い。高競合の操作はキャッシュコヒーレンシプロトコル、とりわけ通信遅延に支配されるためである。

結論は「ロックフリープログラミングは死んだ。ロックフリープログラミング万歳」。すなわち最高競合という伝統的な主戦場はもはや強みではないが、クリティカルセクションを最小化する「ロックフリー的スタイル」は依然として必要で、低競合にはアトミック操作、高競合にはスピンロック、ドメイン間の受け渡しには DCLP、という使い分けに落ち着く。

What Are We Synchronizing? — Robert Leahy

スライド

「何を同期しているのか」を出発点に、値の意味によって必要な同期が変わることを論じる。

オブザーバビリティ用のパケットカウンタのように「値はただの値」なら memory_order_relaxed で足りるが、when_all で最初の例外だけを記録する場合は話が違う。変更順序、read-modify-write が変更順序上の直前の副作用を読むという保証、データ競合の定義、join による同期といった規格の文言で境界を引く。

結論は 4 点 — アトミック性と同期は別物である、同期は happens-before 関係によって与えられる、ロックはクリティカルセクションについて考えさせてくれる、そしてロックフリープログラミングはあらゆる病的な状態について考えることを強いる。

Implementing Async RAII — Robert Leahy

スライド

「コンストラクタはコルーチンであってはならない」「デストラクタはコルーチンであってはならない」という規格の制約のもとで、io_uring の OPENAT/CLOSE のように取得も解放も非同期な資源をどう RAII に載せるかを扱う。

Kirk Shoop の async-object(P2849R0)を踏まえ、enter_scope_sender / exit_scope_sender というコンセプト群と、リフレクションを用いた consteval メタ関数で within と enter_scopes を設計する。

結論は概念の組み替えにある — RAII は本質的にオブジェクトではなくスコープの話である。enter/exit スコープ sender がスコープ境界を非同期領域へ投影し、オブジェクトとは「スコープ+ストレージ」であり、lifetime が非同期操作に本質的に非同期なローカル変数を与える。

Senders, Receivers, and Robots: Structured Concurrency for Autonomous Navigation — Nahuel Espinosa

スライド

「ロボットに何かをさせたい。そして止められるようにしたい」という動機から始まる、ROS の自律走行スタックへの構造化並行性の適用事例。

「計画アクションを呼ぶ → 結果を制御アクションに渡す → コントローラに 60 秒タイムアウトを課す」という単純な要求が、コールバックベースの action_client API ではネストの山になる様子を見せ、sender/receiver で平坦に書き直す。

締めくくりは RPC を超えた実用の形 — トピックを購読し、イベントループを塞がずにワーカースレッドで重い計算をし、結果を publish する、という典型を continues_on でスケジューラを渡り歩くパイプラインとして示し、counting_scope とシグナルハンドラで停止と join まで面倒を見る。

Awaiters and Awaitables — Mateusz Pusz

スライド

コールバックベースの既存 API(OS タイマー、スレッドプールの完了通知、他人の std::future<T>、C ライブラリの関数ポインタ)とco_await を繋ぐ「awaiter」の書き方だけに徹底的に絞った発表。

明示的に「task<T> の話ではない」という立場を取る —std::execution::task<T> が C++26 に入るので promise_type は他人の問題になったが、awaiter だけは自分で書き続けることになる(2029 年になってもベンダ SDK は関数ポインタを取る)。

設計上の指針が 2 つ。awaitable が公開 API で、awaiter は機械であり、コンパイラだけが見るべき 3 つの関数である(operator co_await と await_transform がその分離を担う)。そして対称転送を選ぶこと — await_suspend の中で resume() を呼ぶと入れ子になり、1 万回ループすれば破綻する。「コルーチン連鎖のスタック深さは、ループで何回 co_await したかではなく機械の性質であるべきだ」。

Readable Async Workflows in Modern C++: Coroutines Meet std::expected — Futong Liu

スライド

注文処理(認可 → 読み込み → コンプライアンス確認 → コミット → 通知 → 応答)という同期なら一直線の処理が、コールバック化によって 4 つのファイルに散逸していく様子から始まる。

コルーチン + std::expected で一直線の可読性を取り戻したあと、議論はその先へ進む。when_all でバッチ処理すると「19 件成功・1 件失敗」が返り、戻り値の型に何を入れるかという設計問題が浮上する。答えは std::expected<LoadSummary, LoadError> のように、成功分と失敗分の両方を持つ型を設けること。

通底する原則は、実行方針(どう走らせるか)と結果方針(何を成功とみなすか)を分けること、そして上位層には局所的な詳細を隠し、下位層には判断に必要な文脈を残すという層の設計。

Lock-Free Timer Scheduling With C++ Atomics — Maxim Gurschi

スライド

「毎秒 4 更新なら 1 フレームの予算は 250ms、計算が 100ms なら 150ms は遊んでいる」という UI 更新を、独立インスタンス数千件ぶん捌くタイマスケジューラの設計。Mechanical Sympathy に則りロックフリーで組む。

効果は明確で、work stealing 系のベースラインに対し 100 万件の 1 サイクルのスケジューラ時間が 8.5 秒 → 0.015 秒(550 倍以上)、定常状態で動くワーカーコアは 8 → 1、プロセス CPU 使用率は 200% → 5.8%。ベースラインは 250ms サイクルに追いつけていなかった。

要点は 4 つ — スケジューラは仕事を繰り返し再スケジュールするのではなく所有権を移すことでスケールする、仕事を動かすのは有益なときだけ、正確である必要のない情報は同期しない、そしてホットパスの所有権移動と回収にロックは要らず、慎重にスコープを絞ったアトミック操作で足りる。

Tying up Loose Threads: Making Your Project No-GIL Ready — Charlie Lin

スライド

PEP 703 のフリースレッド(no-GIL)Python に対して、C/C++ 拡張をどう対応させるかの実務ガイド。

インタプリタ側の変更点を押さえる — アロケータの pymalloc → mimalloc と遅延回収、参照カウントの ob_ref_local / ob_ref_shared への二分割、コンテナのオブジェクト単位ロック、そして反復中の変更・read-modify-write・TOCTOU が非アトミックである点(Py_BEGIN_CRITICAL_SECTION で保護する)。

実務面では PyUnstable_Module_SetGIL による宣言、cibuildwheel の cpython-freethreading、-X gil=0 と pytest --run-parallel、TSan/ASan、そして pytest.warns や capsys などスレッド安全でないテスト機能の具体的な一覧が役に立つ。

締めは進捗報告 — PyPI の人気 wheel の 61% が既にフリースレッド互換。だから「依存を上げよう」。

Processor Design and C++ Memory Models — Ofek Shilon

スライド

「メモリ操作が可視になる順序はプログラム順序と異なりうる」という定型句の中身を、プロセッサ設計の側から説明する。

Writer/Reader の 2 コアによる data/ready の例で strict consistency と sequential consistency を対比し、「ではなぜ SC ではないのか」を問う。プライベートキャッシュは答えではない(キャッシュコヒーレンス、MESI がある)。

真の原因として invalidate queue、非 multi-copy atomicity、ライトバッファを順に示し、さらにパイプライン、アウトオブオーダ実行(レジスタエイリアステーブル、リザベーションステーション、リオーダバッファ)まで降りていく。C++ のメモリモデルが「なぜそういう形なのか」をハードウェア側から腑に落とさせるのが狙い。

You Shall Not Pass*! — Daniel Pfeifer

スライド

Sean Parent の "C++ Seasoning" へのオマージュとして 3 つの目標を掲げる —生ループをなくす、生の同期プリミティブをなくす(C++26 実行制御ライブラリによる構造化並行性)、生ポインタをなくす。

ただし Core Guidelines の I.11(生ポインタで所有権を渡すな)と F.7(一般用途ではスマートポインタでなく T*/T& を取れ)の名前の衝突を指摘する。Type* object を見て答えられない問い(delete する責任は誰か、いつダングリングするか、変更してよいか)こそが本質だとする。

結論は、ポインタは — スマートであろうとなかろうと — 関数シグネチャに現れてはならない。通常の値セマンティクスを持つクラスで包むこと。shared_ptr/unique_ptr は有用な部品だが、std::indirect/std::polymorphic はより良い部品であり、そして部品はまだ足りない。仮想関数が全部 const なら shared_ptr<Base const>、そうでなければ std::polymorphic<Base>(著者いわく「NVI 2.0」)。

Beyond the Scheduler: Designing Predictable Embedded Systems in Modern C++ — Elbert Dockery

スライド

「リアルタイムの推論はスケジューラで止まってはいけない」という主張のもと、C++20 で境界のある組み込みファイルシステムを自作した工学的ケーススタディ。

メタデータ・アロケータ・ジャーナルをブロックデバイス契約の上に構築し、C++20(std::array/std::span、RAII、constexpr ジオメトリ、操作カウンタ)で制約を明示的に符号化する。4096 バイトと 4097 バイトで要求される仕事量が 1R/9W と 1R/10W に変わる、という粒度で可視化する。

副産物として、境界を明示したことがメモリ問題も暴いた — create_file() のスタックフレームが12,536 バイト → 264 バイト(約 97.9% 削減)(代わりに FileSystem オブジェクトは 4.9KiB → 9.0KiB。メモリは消えたのではなく明示的で再利用可能になった)。

要点は 3 つ — 時間を測る前に仕事量を束縛せよ(タイミングは何が起きたかを語るが、仕事量モデルはなぜかを説明する)、資源要求を明示せよ、そして予測可能性は層をなす(スケジューラ=いつ走るか、アーキテクチャ=どれだけの仕事か、プラットフォーム=その仕事がいくらか)。


【3. 低レイテンシ・性能最適化】

Writing Low-Latency C++: Predictability, Cache, and the Architectures Underneath — Sampad Acharya

スライド

Bloomberg の債券取引チームによる低レイテンシ実装ガイド。「正しさは前提、良し悪しを分けるのは予測可能性」が主題 —「10% 遅くても予測可能なシステムは、10% 速くてもジッタのあるシステムに勝つ」。

STL コンテナ、メモリレイアウトとキャッシュ、アトミック操作とミューテックスの比較、スレッドとスケジューリングを順に扱うが、OS スケジューラの扱いが他書と異なる。ミューテックスの再取得では直近に走っていてキャッシュにデータが残っているスレッドが選ばれやすく、4 スレッドあっても A→A→A→A と同じワーカーが勝ち続け、B・C・D は走らず、キューは伸び、実質シングルスレッド化する。

締めは計測の心得 — 平均は嘘をつくのでパーセンタイルと分布を見ること、コンパイラフラグ・CPU・カーネル・環境を記録しなければベンチマークは無意味であること、そして「絶対的なベンチマークは幻想であり、結果は測定した環境の中でしか一般化しない」。

The Latency Slippage: Understanding the Hidden Costs of Caching for Low Latency C++ — Sanchit Gupta

スライド

高頻度取引会社のエンジニアによる、ハードウェアの「推測」を暴く発表。CPU は次に欲しいライン(Prediction)、誰がどの状態で持つか(Ownership)、何を残すか(Retention)をヒューリスティックで推測している。

Core i9-14900KS 上で各階層の相対コスト(1 : 3.2 : 14.5 : 103)や「1 コアが同時に抱えられるキャッシュミスは 16 スロットまで」といった事実を確かめながら、1 つのアプリを分解していく。clflushopt は読み捨てるリングに、clwb は他コアが読む直前のラインに適する(クリーンなラインの writeback は書くものがないのでほとんど動かない)。

結果は 1 イベントあたり 510ns → 329ns。しかも計算内容はまったく同じで、変えたのはキャッシュの振る舞いだけ。要点は 3 つ — プリフェッチ距離と対象はアプリの意味論と perf 指標から決めること、clflushopt は 1 命令でキャッシュを空けてストールを減らすこと、そして共有読み出しにはコストがあるので、他コアが読む前に clwb で L3 へ押し出すこと。

When Zero-Cost Abstractions Aren't Zero-Cost — Steve Sorkin

スライド

「使わないものには金を払わない。使うものは手書きより良くはできない」という Stroustrup の約束が成立する条件を明確にする。

核心は「ゼロコストは保証ではなく契約である」。抽象が最適化で消えるには、具体型が見えていること、制御フローが解析可能であること、抽象境界が透過であることが必要で、情報が隠されたり間接参照や動的な振る舞いが残れば、コストは実行時に残る。std::function(型消去による情報の喪失)、Ranges/Views(合成の複雑さ)、段階的コンテナ(所有権と実体化の意味論)と、コストの出どころが抽象ごとに違うことを実例で示す。

「-O3 にすればいいのでは」への答えは — 最適化レベルは抽象が消し去った情報を復元できないし、意味論が要求する仕事を除去できない。しかも無料ではない(コード膨張、命令キャッシュ圧迫、コンパイル時間、時に -O2 より遅い)。

Are You Smarter Than A Branch Predictor? — Michelle D'Souza

スライド

ゲームショー形式(聴衆への質問と景品の消しゴム投げ込み付き)で分岐予測を解説する発表。

分岐予測なしのパイプラインの空転から始め、Fetch 段で Branch Target Buffer を引く仕組みを見せる。現代のプロセッサは予測ミスでパイプラインをフラッシュし正しい経路から再実行するために 10〜20+ サイクルを失い(2.2GHz で 20 サイクル ≒ 9ns)、その経路でキャッシュミスが起きればさらにメモリレイテンシを払う。

締めは最適化の方法論 — 分岐を節約せよ(エラーコードのチェックを減らす、ただしモナディックな書き方にも同じ問題がある)。そして perf stat で IPC・分岐ミス率・キャッシュミスを変更の前後で定量化し、Celero や Google Benchmark で end-to-end を測り、本番に似たデータで測り、PGO で実際の分岐傾向をコンパイラに教えること。

The Greatest Misconception in Computer Science — Alex Dathskovsky

スライド

「O(1) < O(log n) < O(n) < … という表だけで速さを判断する」という計算機科学最大の誤解を扱う。

計算量の合成(O(N) + O(N²) + O(log N) ⇒ O(N²)、「アルゴリズムは最悪の部分と同じ性能しかない」)と償却計算量を確認したうえで、話は CPU の進化へ移る — パイプライン、アウトオブオーダ実行、分岐予測、SIMD、分岐レスプログラミング、キャッシュ、マルチコア。ヒストグラムを unordered_map と vector で実装して比べると差は歴然で、漸近計算量は同じでも実測はまるで違う。

そして「A から B の要素を順序を保って取り除く」という一つの問題に対し、ソート+二分探索、密なバイト探索、ビットマップ、Bloom フィルタ、ソート済みバケット探索、バイト集合バケット、ビット集合バケットと7 通りの実装を実測で比較する。結論は「力はあなたの手にある」— 計算量ではなくハードウェアに合わせて設計せよ。

What Is Your Algorithmic Core? — Egor Suvorov

スライド

「アルゴリズムはテストしにくく、頑固で、バグは表に出るまで隠れている」という観察から、コードからアルゴリズム部分を抽出せよと説く。

2 種類のバグを対比する。ラムダの参照キャプチャによる use-after-free のようなエンジニアリングのバグは行カバレッジで捕まるが、KMP による部分文字列探索のバグは違う — LLM が生成した 17 個のブラックボックステストで行・分岐 100% カバレッジを達成しても、1 行消してもテストが落ちない。

後半は仕様の不足を列挙する — 「最長増加部分列」と気づかずに貪欲法で誤る例、非機能要件(コールバック数、確保回数、償却か最悪か、「線形」とは何の線形か)、出力引数の追記か上書きか、自己部分オブジェクト代入。そして「正確な契約が不可能なこともある」と認める(CSV のエスケープ、ファイルシステムの可視性、SQL の既定分離レベル、浮動小数点の印字)。

Same Bits Without Losing MIPS: Reproducible Numerics at Full Hardware Speed — Andrew Drakeford

スライド

ハードウェア・スレッド数・命令セット・ツールチェーン、さらには実行ごとに答えが変わる問題に対して、達成困難な目標を立てて挑む —llama2.c を Apple M4 / Ryzen 9 7900X / RTX 5070 上でビット単位再現可能に、かつ遅くせずに動かす。

結果は 13 構成・0 不一致(1 実行あたり 12,864 チェックポイントをハッシュ)で全構成が同一トークン列。しかも性能はむしろ向上する(Ryzen AVX2 1 スレッドで 131 → 479 tokens/s)。手法は非決定的な数値要素を切り出してバックエンドを差し替え可能にし、契約を「式を固定し、実行を変化させる」と定めること。

主張は標準化に向かう。決定的な数値契約が規定すべきは、要素演算の意味、評価すべき式または厳密な中間状態、そして観測すべき結果。規定するのは表現と演算意味論と観測間の整合性だけで、スレッド数・スケジューリング・分割・タイリング・CPU/GPU の実現は自由にしておく。P4016R0(canonical reduction)と P4229R0(canonical scan)として提案中。

Death by a Thousand Tape Entries — Jorg Lotze

スライド

金融・科学計算向けの随伴自動微分(AAD)で、テープのオーバーヘッドを削る 10 個の設計ルール。

CVA 評価(モンテカルロ 10,000 パス、40 入力)では、バンプ&再評価が 41 倍、ナイーブなテープの AAD は 55 倍でバンプ&再評価にすら負けるのに対し、XAD は 3.9 倍(理論限界は 4 倍)。まとめは「55 倍が 3.9 倍になったのは、テープの 1 エントリを安くし、そのうえでエントリの 3 分の 2 以上を削ったから」。

ワークロードによる差も分析する(LIBOR 6.7 倍、Heston 2.9 倍)— 前者は primal が 1 サイクル 4 命令で余裕がなく、後者は除算と exp の完了を待つため、その待ち時間にテープの処理が収まる。実証は QuantLib(30 万行、Real を XAD の型に束ねただけで書き換えなし)で、69 個の感応度がバンプ&再評価の 69 倍に対し 1.3 倍。

std::simd Without Compromise: SIMD in C++26 and Beyond — Ruslan Arutyunyan, Daniel Towner

スライド

30 年以上プロセッサはスレッドではなくデータ並列性で並列能力を高めてきたのに、C++ にはそれを表現する標準的で可搬な語彙がなかった — C++26 でそれが変わる。

SIMD がパイプライン税(fetch/decode/schedule/speculation/retire)を償却する仕組み、3 つの実行次元(垂直/map、水平/reduce、shuffle/permute)、各レジスタ幅とマスキングを押さえたうえで、std::simd の実用形(unchecked_load と partial_load+mask_from_count による端数処理)を示す。

可搬性を掲げつつイントリンシックからの逃げ道も用意するのが現実的で、C++26 ではネイティブ幅の組み込み型との直接変換、C++29 では任意幅を扱う chunked_invoke と共通イントリンシックのラッパを提案する。rebind_cast は simd に限らず std::array や std::complex にも使える語彙として提示される。実装 xvec はオープンソース。

musttail-ling Our Way to a Faster Python Interpreter and New JIT Compiler — Ken Jin Ooi

スライド

インタプリタという構造は言語処理系だけでなくパーサ、シリアライザ、正規表現エンジン、状態機械全般に現れる、という導入からCPython 3.14-15 の [[musttail]] インタプリタと新しい JIT コンパイラを解説する。

従来の switch-case 方式(1 命令あたり 2 ジャンプ)、computed goto 方式(1 ジャンプだが 12,000 行超の巨大な単一関数になり、レジスタ割り付けを制御できず、コンパイラバグを踏みやすい — LLVM/GCC の実際の issue 番号付きで列挙)の問題を示し、末尾呼び出し保証によるディスパッチへ移行する経緯を辿る。

Modern C++ Techniques to Reduce Boilerplate Without Sacrificing Performance — VISHNU G NATH

スライド

イテレータの明示的ループのうち本質は Process(*it); の 1 行だけで、残りは「言語の儀式」である、という指摘から始まる。

「ポインタ+サイズ → std::span」「nullptr → std::optional」「イテレータループ → Ranges」「SFINAE → Concepts」という対応表を示し、中心的な主張を「モダン C++ は複雑さを取り除くのではなく、開発者からコンパイラへ移動させる」と置く。各技法は「何を得て、何を払ったか」を実行時間・コンパイル時間・アセンブリ・バイナリサイズの 4 軸で測る(そのために CompBenchmark という計測フレームワークを自作している)。

導かれるのは 11 の工学原則 — 意図を書け機構ではなく、共通パターンを標準化せよ、不正な状態を無視できなくせよ、状態を型システムでモデル化せよ、資源の寿命をオブジェクトの寿命に結びつけよ、そして最後が「最適化の前に測れ。証拠は直感に勝る」。

Practical HPC — Forcing the Compiler's Hand with Modern C++ — Marco Barbone

スライド / コードとベンチマーク

FINUFFT の手作業アンロール(issue #459)から生まれた、「性能を買うかわりに毎日利息を払う 6 つのパターン」の実測比較。手動アンロール、マクロ、#pragma、手書きの if 連鎖によるディスパッチ、インライン asm、コード生成を順に検証し、#pragma GCC unroll が速度向上ゼロ(ループは展開されてもリダクションは展開されず、浮動小数点加算は結合則が成り立たないため再結合が許されない。警告も出ない)といった落とし穴を暴く。

後半は同じ性能を標準機能で取り戻す — C++17 の integer_sequence と畳み込み式、C++20 のテンプレートラムダ、C++26 の template for、畳み込み式によって if 連鎖を書かずに済ませるディスパッチ、そして std::simd。

最終的に ISA 名がソースに一切現れないまま、手書きアセンブリ(25.9 倍)の 6% 以内の 24.6 倍に到達する。

What 25 Days of Optimisations Taught Me About Compilers — Matt Godbolt

スライド

「Advent of Compiler Optimisations」24 本の記事と動画の総括。結論は「コンパイラが賢くないのではなく、情報が足りないのだ」。string_view の比較が重なった 4 バイト読み 2 回に化ける例、再帰 gcd がスタックを使わないループになる例から、Counter<int> と Counter<long> で後者のほうが速い(int 同士はエイリアスしうるので毎回ストアが必要)というエイリアシングの話から、++num_compares を 1 行足すだけで strlen がループ内に戻る話、gcc の部分インライン化まで。教訓は「許されていなかった(規格を確認せよ)」「知らなかった(情報を与えよ)」「そう判断した(測定せよ)」の 3 分類。

企画中に実際に報告したコンパイラバグ(gcc #122315 ほか)を再訪し、バグ報告を呼びかける。

"If You Can't Measure It, You Can't Improve It": From Profiling to Automatic Benchmarking — Kris Jusiak

スライド

linux-perf を軸にした計測手法の総覧。「決して throw されない try/catch を足したら?」といった "what if" ルールで問題意識を作り、各階層の実測コスト(L1d 0.75ns、分岐予測ミス 3.50ns、DRAM 70ns)を示す。

Top-Down マイクロアーキテクチャ解析、サンプリング(perf + FlameGraph)、インストルメンテーション(bpftrace、llvm-xray)、トレーシング(Intel PT)、機械語解析(llvm-mca)、シミュレーション(callgrind)、ベンチマーキングを順に実演。

後半は「間違ったものを測れば正しいものは改善できない」としてコード配置由来のバイアスとノイズを示し、angr と Unicorn によるシンボリック実行で全経路を非侵入的に計測する Symbolic Benchmarking を提案する。

From 20 Nanoseconds to One: Optimizing Bishop, Rook, and Queen Move Generation in a Chess Engine — Aryan Naraghi

スライド

自作チェスエンジン FollyChess における、スライド駒の指し手生成の最適化記録。Bitboard(駒の存在を表す 64 ビット整数)と素朴なレイ探索(ルーク 22.6ns)を出発点に、4 つの手法を検討する。

総当たり表は約 9.44 ゼタバイト(世界のストレージの約 10%)で却下、relevancy mask + ハッシュマップは 2.5MB だがホットパスにハッシュが乗り 6.6ns、PEXT は速いが x86 限定。

最終的にマジックビットボード(占有 × 乱数マジックの乗算シフトハッシュ)で 0.9ns・約 24 倍・2.1MB・可搬を達成する。マジック候補が「3 つの乱数の AND」で疎に生成される理由も解説。ただし Amdahl の法則により探索全体では 1.06〜1.30 倍にとどまることも示す。


【4. ビルドシステム・モジュール・ツールチェーン】

CMake: Making Hard Things Easy — Bill Hoffman

スライド

1999 年に CMake を作った Kitware 創業者・CTO による発表。「難しい問題 → 専門家チーム → オープンソースの解」という働き方の中で、ビルドもその問題の 1 つだったという経緯を語る。

CMake の 4 つの時代(2000 年のコンパイラフラグの寄せ集め、2004 年の普及、2014 年の target_* コマンド、2025 年の相互運用と標準化)を辿り、1 つのコンパイラコマンドからビルドエコシステム全体へ、そしてビルドシステムの生成からツールチェーン全体が消費できるフォーマットの定義へという流れを示す(CPS、SBOM、モジュール、計装、リモート実行)。

行動喚起は 4 点 — 要件をターゲットでモデル化せよ、下流の利用者に必要な知識を渡せ(CPS を publish し、モジュールを試せ)、ビルドを計測してボトルネックを見つけよ。いずれも「専門知を一度書き留め、再現可能にし、どこでも動き続けるようにする」という一点に帰着する。

Building C++20 Modules: The Rest of the Story — Vito Gamberini

スライド

C++20 モジュールを「どうビルドするか」に特化した発表。各 TU が異なるマクロ定義・インクルードパス・さらには異なる -std= を持つという現実から問題提起する。

翻訳単位の分類(モジュール単位でない TU、ヘッダ単位、名前付きモジュール単位、パーティション、実装単位)を整理し、具体的なコマンド例と、現在の一般的な合意手法、そして未解決の問題を扱う。

価値があるのは終盤の落とし穴集 — インタフェースのみのライブラリでundefined reference to 'initializer for module math' になる件、パーティション実装単位で初期化子が多重定義になる件、import :detail; した型が「math から import されていなければならない」と怒られる件など、実際に踏む地雷が再現コマンド付きで並ぶ。ビルドシステム側の答えとして CPS の cpp_module_metadata にも触れる。

Using Modules in a Real Project — Erez Strauss

スライド

「C++ モジュールは使えるが、統合には手間がかかる」という立場からの実務レポート(C++26・CMake・Clang・GCC)。tally という台帳ライブラリで、テンプレート・アウトオブライン定義・マクロ・ヘッダオンリー依存という「最初の 1 週間の壁」を実演する。

#include と import の違いを表で整理し(マクロは渡らない、BMI は既にパース済み、インタフェース BMI はビルド順の辺になる)、CMake 側の作法(project() の前に固める、FILE_SET CXX_MODULES、Ninja、import std、非モジュールターゲットではスキャンを切る)を示す。

「上司にこう言え」というまとめが実務的で — GCC 16 はまだ -fmodules が必要でプライベートモジュールフラグメントがない、CMake の import std は実験的、BMI はコンパイラ・フラグ・標準ライブラリをまたいで可搬ではない(だから .pcm ではなくソースを配布する)、全 C++20 ファイルのスキャンは実コスト、include-after-import は一回限りでなくバグの一類型。結論は「モジュールは 2025 年頃に実験的でなくなり、エコシステムは使える。ただし、手間をかけずに済むところまでは来ていない」。

My DAG Ate All My Cores: Identifying and Resolving Build Graph Bottlenecks — Florent Castelli

スライド

「128 コア買ったのにビルドは最大でも 40 コアしか使っていない。問題はマシンではなくグラフだ」が出発点。コードベースをファイルの階層ではなくターゲットの有向非巡回グラフとして見る視点を与える。

おなじみの苦痛をグラフの問題として言い換える —「ヘッダ 1 つで 400 ファイル再ビルド」→ ファンインの大きいノード、「96 コアの CI が半分遊んでいる」→ クリティカルパスが長い、「リンク順が問題になる」→ 認めていない循環。ハードウェアを増やして速くなるかは 3 つの数字で決まる。総作業量 ÷ クリティカルパスがコア数を下回るなら、次のマシンは遊ぶ。

対策は速度ではなく形を変えること(パスを短くする、辺を消す、畳む。PRIVATE 1 つで形が変わる)で、変えるたびにスラックが移動するので測り直す。そして退行防止として、クリティカルパス予算・ファンイン上限・禁止辺・並列度下限を CI でラチェットする。ツール NinjaScope は .ninja_log から単一 HTML レポートを生成し、標準ライブラリのみで依存なし。

Works on My Machine, Works in CI: Treating Your Build Toolchain as a Dependency — Luis Caro Campos

スライド

requirements.txt や cargo.toml に相当するものが C/C++ には長らくなかった、という話から始め、vcpkg・Conan・apt のワークフローを比較する。

主題は ツールチェーン自体を依存関係として宣言すること。ツールチェーンは往々にして手動セットアップで開発者ごとにバージョンが微妙に違い、しかも自動 SBOM 収集からも漏れている。Conan では [tool_requires] に gcc/13.1.0 や msvc/19.44 と書くだけでよく、プロファイルが「ターゲット(host)」と「ビルドする機械(build)」を分けて持つので、クロスビルドも自然に表現できる(macOS から Linux/aarch64 へ、など)。

利点と欠点を両方挙げるのが誠実で、ツールチェーンのドリフトが「Conan プロファイルのドリフト」に変わるだけだが、そちらには既に道具がある。ただし商用ツールチェーンでは EULA を確認せよ、IDE 側の制約もありうる、と断っている。

Component based or monolithic development for large C and C++ projects: Why not both? — Diego Rodriguez-Losada Gonzalez

スライド

Conan の作者による、モノレポ型とコンポーネント型(マルチレポ)開発の対立を解きほぐす発表。Conway の法則から説き起こす。パッケージが進化したときの問題を 2 つに分ける — API 互換な変更は CI の問題、API 非互換な変更は開発者ワークスペースの問題。

CI 側では効率・高速・安全・保守可能という条件を立て、「パッケージのマルチリポジトリ=ソースのマルチブランチ、プロモーション=マージ」という対応を示し、消費者側から辿る方法はスケールしないので「組織の products を選び、各 product の依存グラフをロックファイルで計算し、統合して必要なビルドを順序付きで返す」方式(conan graph build-order、GitHub Actions 200 行で実装可能)を提案する。

ワークスペース側は editable パッケージと、オーケストレーション型・スーパービルド型・動的ワークスペースの 3 方式を比較する。

Automatic Splitting of Translation Units: Compiler-Agnostic Compiler Frontend Parallelization — Damien Buhl

スライド / cpp-splitter

「ビルドはグラフであり、コンパイルエッジは原子である」— その内側はビルドシステムにもキャッシュにもスケジューラにも見えない。unity build が原子を大きくする(融合)のに対し、本発表は逆向きに関数定義 1 つにつき 1 ピースへ分裂させる。

cpp-splitter は libclang で TU を書き換えてピースを並列コンパイルし、ld -r で元どおり単一オブジェクトに再結合する。CMAKE_CXX_COMPILER_LAUNCHER として透過的に挟まるので、ビルドシステムからはエッジが 1 本に見えたままになる。

C++20 モジュールにも適用する — -fmodules-reduced-bmi でも clang は ODR ハッシュを BMI に格納するため本体編集で全 importer が再ビルドしてしまうが、宣言のみに書き換えた interface をプリコンパイルすれば BMI をバイト単位で同一に保てる。

ベンチマークは Boost.Spirit、OpenCV、p4c をリモート実行クラスタ(-j500)上で計測し、バイナリ等価性も検証する。

Running Compiler Explorer in 2026: Sandboxes, Storage, and Strangers' Code — Matt Godbolt

スライド

2012 年に「range-for は遅いかもしれないから使うな」と言われたことに始まる、15 年続く Compiler Explorer の運用記(結論のスライドは「実は range-for のほうが速い」)。現在は週 100 万回超のコンパイル、14 年以上有効な恒久短縮リンク。

サンドボックスでは、LD_PRELOAD による open の差し替えから始まり、-specs= を使った実際の攻撃(/tmp が noexec でも通る)がフラグのブラックリストを生んだ経緯、Docker を退けた理由、firejail を経て nsjail に至る多層防御を語る。

ストレージではコンパイラ 6,236 個(2.4TB)を NFS から squashfs ベースの cefs へ移し、autofs による遅延マウントでマウント数 2,182 → 約 30、起動時のマウント時間 30〜50 秒 → 18 秒に短縮した過程を示す。

最後に Compiler Explorer, LLC の収支(年間 収入 9.4 万ドル / 支出 5.3 万ドル)も公開する。

Prioritizing Pretty-printers — Braden Ganetsky

スライド / k3::gdb

WG21 書記でもある LSEG のエンジニアによる、GDB の pretty-printer を「書く・配る・テストする」まで通した発表(本人いわく「C++ そのものよりツーリングの話」)。

セットアップ(.gdbinit、カスタム printer の読み込みと配線)、to_string() と children() による基本的な書き方を押さえたうえで、配布では install ルールに入れてもなお利用者が source printer.py を要求される問題を指摘し、auto-loading を導入する。

テストでは、Boost.Unordered が所定のブレークポイントで表示を目視確認する手作業だったことを出発点に、libc++ の方式を下敷きにした CTest 互換の自動テストライブラリ k3::gdb を構築する。

締めは「私たちの仕事はソースコード以上のもの、人に関わるものだ」。


組み込み・リアルタイム

Memory Safety on a Microcontroller: What Embedded C++ Teams Can Actually Do Today — Darijo Topic

スライド

Cortex-M33(MMU なし)・Zephyr 4.4・GCC 14.3 という具体的な環境で、組み込みチームが今日実際にできるメモリ安全対策を扱う(全 109 枚、数値はすべて Compiler Explorer で再現可能)。

議論の前提として PC / Linux SOM / マイコンの三列比較表を示し、既存のメモリ安全性文献が前提としない第 3 列(RAM 数十〜数百 KB、フラットな物理アドレス空間、OS は自分自身、更新は届かないかもしれずブリックは出張修理、障害時は物理的被害、デバッグはフォルトハンドラとオシロスコープ)で話す、と宣言する。

対策の提示の仕方も定めていて、コスト・効果・適用条件の 3 つを揃えて語れという —「コード変更ゼロ、チェックあたり 10 バイト、範囲外書き込みを防ぐ」。CISA のロードマップ要件に対しても、Rust について「まだ。毎年見直す。理由はこれ」と明示的に答えよと説く。締めは 6 段のループ — 知る → 検査する → 制約する → 測る → 観察する → 繰り返す。「それがロードマップであり、チェックリストはその時点のスナップショットにすぎない」。

AEMBER: Modern Embedded C++ Without the Chaos — Arian Ajdari

スライド

1.1MB・C++26・起動 10 秒未満・P2996 リフレクションを使う PID1(OS が最初に起動するプロセス)バイナリ AEMBER の紹介。「組み込みのアプリロジックを書きたいのに、init システムのブートストラップと配線とデバッグに時間を取られる」という問題設定。

PID1 の責務(孤児プロセスの回収、SIGTERM の処理、クラッシュすればカーネルパニック)を押さえ、signalfd + epoll による取りこぼしのないシグナル処理、LXC コンテナを第一級の監視対象とするオーケストレーション、Qt ダッシュボードによる死活監視を解説する。数字は実測で、1.1MB バイナリ・RSS 6.6MB(対する systemd は RSS 30〜50MB・C 150 万行のモノリシック)。

要点は 5 つ — C++23/26 は組み込みで実用段階にある(std::expected、jthread、stop_token、P2996)、PID1 には sigwait より signalfd が適し、signalfd と reaper スレッドの二重回収で取りこぼしを防げる、LXC + squashfs は軽量な組み込みコンテナになる、そして「開発者体験は機能である」(devcontainer + QEMU で 5 分で貢献できる)。

From Firmware to Screen: Real-Time Control, Simulation, and Visualization in C++23 — Kevin Gomez

スライド

リアルタイムシステムが「スケジューリング・シミュレーション・運用・可視化」の 4 つの道具の継ぎ接ぎになっており、バグは継ぎ目で生じるという問題提起から始まる —アルゴリズムは 2 回書かれて乖離し、テレメトリのフィールドは 3 回定義され、時間の意味は各ツールで異なり、テストはツール境界で止まる。

解決は、すべてを 1 つの共有 C++ ランタイム上のコンポーネント(SW_MODEL / HW_MODEL / DRIVER / SUPPORT)として構築し、全コンポーネントに同じライフサイクル・スケジューリング・パラメータ・コマンド・テレメトリを与えること。

説得力は実機デモにある — NUCLEO-F767ZI 上の誘導則を 20Hz で動かし、lidar は専用線で実シリアルフレームを 10Hz で送り、地上局から LED 点灯・経路実行・途中停止・lidar 切断(2 秒以内に sensor lost で停止)まで操作する。ボード側の最近傍距離は 1,915 サンプル中 1,867 でプラント側と一致し、残りも 1 スキャン遅れているだけだった。

Capability Routing Grid: From Decoupled Plugins to the Hardware Ceiling — Cyril TISSIER

リポジトリ(GridZero) / スライド

Ubisoft が公開した C++17 ヘッダオンリーのツールキット GridZero の発表。厳密に分離されたプロジェクトをまたいだ疎結合な合成を目的とする。NodeLink は実装が中央レジストリも Init() 呼び出しもなしに、DLL 境界を越えてさえ自己登録する仕組みで、これだけでプラグインシステムが作れる。

もう一方の capability router は、振る舞いをモデル型に結び付けるのに仮想関数テーブルではなくフラットなテンソル参照を使い、仮想メソッドを持たない契約なら仮想呼び出しゼロ・ヒープ確保ゼロで済む(「ハードウェア限界まで」はこの意味)。

構成は Discovery / Models / Capabilities / Type erasure の 4 層で、リポジトリにはフレームワークを層ごとに積み上げる 9 段階の実行可能なテストステージが含まれる。


【5. Back to Basics】

Back to Basics: Move Semantics — Ruslan Arutyunyan

スライド

C++ 委員会 SG1 議長による ムーブセマンティクスの基礎。まず C++11 が追加したものを確認する(右辺値参照、拡張されたオーバーロード解決、標準ライブラリ対応)。

a、a = b、1、a + b、a++、++a、foo()、bar()、"hello" といった式を 1 つずつ lvalue / rvalue に分類していき、式のカテゴリを体で覚えさせる(すべて Compiler Explorer のデモ付き)。

終盤は完全転送で、参照の折りたたみの規則(T& & → T&、T& && → T&、T&& & → T&、T&& && → T&&)を導出し、まとめは 3 点 — 転送参照は型推論を伴うジェネリックなコードにのみ現れる、転送参照が std::forward を可能にする、そして std::forward は条件付きキャストである。

Back to Basics: Lambdas, Function Objects, and std::function — Roth Michaels

スライド

「関数とは本当は何か」という問いから始まる。数学の関数は副作用のない写像だが、コードでは効果のためだけに存在するものも関数と呼び、C++ では少なくとも 6 種類のものが「関数」と呼ばれる。

そこから関数ポインタ・メンバ関数ポインタ、operator() を持つ関数オブジェクト、より親しみやすい顔をしたラムダ、型消去としての std::function とそのコストへと進み、著者が実際にリリースしてしまったバグも共有する。

おまけの Objective-C ブロックとの比較まで進む。^{ } は [=] に相当し、捕捉はデフォルトで値かつ const、__block だけが例外。ブロックリテラルはスタック上にあるので保存には Block_copy(または ARC)が必要になる。そして self を捕捉すると retain される — これは shared_ptr を捕捉したときの循環とまったく同じバグで、__weak/lock() という同じ直し方をする。

Back to Basics: Function Overloading — Chris Ryan

スライド

WG21/EWG メンバによる関数(および演算子)オーバーロードの基礎。「コンパイル時多相 — 呼び出しの引数をシグネチャに照合して、コンパイラが呼ぶべき関数を選ぶ」という定義から入る。

引数の個数・型・順序が違う 3 パターンを実例で解説し、規則と落とし穴を押さえる —戻り値型はオーバーロードの区別に使えない、デフォルト引数は曖昧性を生む。

後半は演算子オーバーロード、とりわけ変換演算子に紙幅を割く。レガシー C 関数に渡すための operator const char*()、スマート参照の operator T*()、そして explicit operator bool() が if(db) を許しつつ int x = db; をエラーにする例。「単純化による複雑性の削減」が著者の信条。

Back to Basics: Code Analysis — Alexsandro Thomas

スライド

湿度データの統計を出すだけの小さなプログラムを題材に、静的/動的解析を学ぶ。「4 件読んだのにヒストグラムは 3 件」「空入力が arm64 では 0 終了、x86-64 では SIGFPE」という実演から未定義動作を導入し、Rice の定理により「どんなツールもすべてには答えられない」という限界も明示する。

終盤には AI による解析の実測比較まである。決定的なツールが報告しなかった指摘(平均値の暗黙の切り捨てなど)を AI は見つけた一方、人間のレビューは 118 行で 18〜53 分・20〜80 ドル、AI は 40〜120 秒・0.05〜0.43 ドル。それでも「人間のレビューはどのモデルにも出せないもの — 設計への反論、文脈、当事者意識 — をもたらす」と結ぶ。

結論の表では、各手段が「何を証明するか」と「何については何も言っていないか」を並べる —警告なしは「有効にしたチェックが発火しなかった」だけ、カバレッジ 100% は「全行が走った」だけ、PR 承認は「レビュアーが問題を見つけなかった」だけ。どの一手も他を置き換えない。

Back to Basics: std::unordered_map — Kevin Carpenter

スライド

「何十年も std::vector と std::map の 2 つで C++ を教えてきた。それは間違いだ」— 1,000 万要素で map の検索は約 22 倍遅い、という導入。

まず両者がいかに似ているか(宣言行より下は一字一句同じ)を示し、だからこそ 1 行の差し替えが間違いやすいと警告する。内部は「ハッシュ → 剰余 → バケットを走査」だけであり、バケットはヒープノードの連結リストの先頭で、要素ごとに 1 確保、アドレスは散らばる。そしてこのレイアウトは手抜きではなく、バケット API と参照の安定性を守る限り他の適合設計はありえない。

規則は 4 つ — 出力順に依存する map を差し替えない、ハッシュは識別に使う全フィールドを hash_combine する、自作キーでは Hash と KeyEqual が一致していなければキーを失う、サイズが分かるなら reserve() を呼ぶ。加えてスレッド安全ではない(同時 read+write は UB)。結論は「『念のため』で std::map を使うのをやめよう。unordered_map から始めて落とし穴を押さえ、読み取り主体で一度きりのロードなら flat map を検討せよ。そして測れ — コンテナがボトルネックであることは稀だ」。

Back to Basics: Memory Alignment — Sarthak Sehgal

スライド

CPU のメモリ階層(レジスタ ~1 サイクル、L1 ~4、L2 ~12、L3 ~40、RAM ~300 サイクル ≒ 100ns)から始め、キャッシュラインという転送単位、時間的局所性と空間的局所性、int x = array[5]; で 64 バイトが運ばれる様子を追う。

そこからコンパイラのレイアウト(パディングとパッキング)、alignas/alignof/sizeof/std::align による制御へ進む。

終盤の主役はフォルスシェアリング — 別コアのスレッドが同じキャッシュラインの別変数を書くと、互いに無効化を繰り返してラインが往復し続ける。alignas(64)(C++17 なら std::hardware_destructive_interference_size)で各変数を独立したラインに置けば解決する。まとめは、CPU はアラインされたアドレスで最も効率よく動く、キャッシュライン境界をまたぐと遅い、構造体のアラインメントは最大メンバのそれになる、packed 構造体とビットフィールドは空間を節約するが扱いに注意が必要で、packed メンバへのポインタは「packed である」という情報を失うので memcpy を使え。

Back to Basics: Text Formatting — Ben Saks

スライド

C の <stdio.h>、C++ の <*stream> / <iomanip> という既存手段を振り返ったうえで、<format>(C++20)と <print>(C++23)が何を改善したのかを示す。

構成は I/O の基礎、引数インデックスと動的な書式文字列、そしてユーザー定義型の書式化。printf の書式指定子の復習から入り、std::print が何を引き継ぎ何を変えたかを対比する。

後半は自作の formatter を最後まで書き切る。分数型に {:>3}/{:>4} のような幅指定を与えるために、parse が std::tie でイテレータを進めながら幅を読み、/ がなければ std::format_error を投げ(コンパイルエラーになりうる)、parse は必ず閉じ } を指すイテレータを返さなければならない(さもないと「閉じ } が見つからない」と言われる)。そして実行時に組み立てた書式文字列を std::vformat_to に渡す、という実装まで辿り着く。

Rule of 0,1,2,5,6,7,8,9,10? — Jason Turner

スライド

C++ Weekly の Jason Turner による、特殊メンバ関数と「〜の規則」の総ざらい。「調べ始める前は分かっているつもりだったが、気づいたら思いのほか深入りしていた」という導入から、特殊メンバ関数はいくつあるのかを問い直す。

結論は時代ごとに整理される。特殊メンバ(C++11)は「rule of 0」を優先し、資源を管理するなら 6 つすべてに賢く対処し、&& 修飾の代入演算子を提供しない(C++29 で A& operator=(const A&) && = default のような宣言は禁止される方向)。比較(C++20)は <=> か == のどちらかを常に default とし、自前の <=> を書くなら == も書く。後置演算子(C++29)は前置を定義したら後置を default し、[[nodiscard]] を付ける。

そして全演算子に共通する助言として、明示的に定義・default したものは[[nodiscard]]・constexpr・文脈次第で noexcept を検討せよ。一言でのまとめは「特殊メンバ・比較・前置演算子について、意図的であれ」。

Back to Basics: C++20 Concepts — Amir Kirsh

スライド

CoreCpp カンファレンス共同主催者による「Concepts 101」。制約、まともなコンパイルエラー、SFINAE に代わるオーバーロードの区別、そして「契約による設計」に近い強制力のあるドキュメントという利点から入る。

「反復可能な型」と「それ以外」で print をオーバーロードする課題を、enable_if_t による SFINAE 解(動くが 🤮)と比較しつつ、requires 節・template<std::ranges::range T>・abbreviated function template の 3 つの書き方で解いていく。

標準の Concepts ライブラリを概観し、concept Meowable = requires(T t) { t.meow(); }; のような自作コンセプトとif constexpr(Meowable<T>) としての使い方まで進む。

「Concepts は純粋なダックタイピングであり、特定の実型・基底クラス・インタフェースを要求しない」という位置づけを強調する。


【6. ライブラリ設計・API 設計】

Why Great C++ Libraries Fail (And How to Fix That) — Mateusz Pusz

スライド

「技術的卓越性 ≠ プロジェクトとしての卓越性」が主題。最先端のテンプレートと concepts を持ちながら最先端の苛立ちも提供しているライブラリを「要塞」と呼ぶ(入りにくい、理解しにくい、防御的なメンテナ、CONTRIBUTING.md も good first issue もない)。

ユーザージャーニーを Discovery から Community までの 6 段階に分けて 11 の施策を示す。命名は技術的判断である(units のような一般名を避ける。自身の mpusz/units が nholthaus/units と競合した経験)、リポジトリ名・CMake ターゲット・パッケージ名・名前空間を揃える、README の 5 秒テスト。

メンテナ側への助言も厚く、PR レビューではまず感謝し、設計判断を説明し、拒絶ではなく成功へ導くこと(「説明のない No はすべて、次の貢献を遠ざける」)。そして「ドキュメントを書くと API の設計欠陥が見える。説明しにくいなら、その API はたぶん間違っている」。行動喚起は「11 個を一度にやるな。今週ひとつ選んで 2 時間使え」。

Ranges Without Compromises: Designing for Simplicity, Performance, and Composability — Oleksandr Bacherikov

スライド

生ループ/アルゴリズム/range views を同じ問題で比較し、views が約束した「再利用可能で合成可能なアルゴリズム」がどこで崩れるかを示す。v | drop_last(2) | transform(fn) | filter(check) という一見きれいなパイプラインにも、fn の重複呼び出しや size < 2 のときの挙動という穴がある。

技術的な中心は式の書き換え規則で、reverse(filter(x)) を filter(reverse(x)) に、transform(f1) | transform(f2) を transform(f2∘f1) に、filter(p1) | filter(p2) を filter(p1 and p2) に簡約する。欠点も挙げる。STL にない技法なので教育が必要で、簡約後は型が変わり、N 個の view に対し N² 通りの組み合わせを扱う必要がある。

行動喚起は踏み込んでいる —「std::ranges::views を使うな」(range アルゴリズムは問題ないが、合成可能ではない)。本番には Flux、実験には rivers。そして view を増やすより内部イテレーションの標準化に資源を割き、標準コンテナとアルゴリズムをイテレータからインデックスへ移す道を議論すべきだ、と主張する。

Beyond Monads: Pattern Matching Alternatives for Result Types — Vitaly Fanaskov

スライド

std::expected<T, E>、std::optional<T>、std::variant<L, R> はすべて直和型であり、std::variant で実装できる。この整理が議論の土台になる。

.and_then(...).or_else(...) が好ましい場面がある一方、モナディック操作が冗長になる場面を挙げる —トレースやログ、別種の API へオブジェクトを渡す、オブジェクトやエラーを直接印字する、非同期 API、Qt の signal/slot、ユニットテスト。いずれも「中身を取り出した後で使わない」ケースで、std::visit 相当のほうが素直に書ける。

結論は 3 段構え —結果型に std::visit を適用できる(ただし別の名前空間のヘルパとして)、取り出した値をその後使わないときはより単純で明示的なコードになる、そして本来の最善手はパターンマッチングだが、提案の実現は「とても、とても遠い」ので、多くの人は当面使えない。

Using Type Erasure to Extend APIs You Don't Own — Jan Wilczek

スライド

オーディオプラグイン開発を題材に、自分が所有していない API を型消去で拡張する事例研究。JUCE のパラメータ階層(AudioProcessorParameter から Bool/Int/Float/Choice などが派生する複雑な木)を前に、そこへ共通操作を足したいが基底クラスには手を入れられない、という状況から出発する。

結論は型消去の適用条件を明確に述べる —あらかじめ型が決まっていない(あるいは型を制御できない)強く型付けされたオブジェクトの集合を管理し、その全要素に共通の操作を行いたいときに型消去は良い選択になる。

言い換えれば、継承もテンプレートも使わずに多相的な振る舞いを得るため、そして型とその操作を物理的に分離するために使う — それが結果としてサードパーティ製フレームワークの制約を越える手段になる。

Compile-Time Polymorphism for Runtime-Flexible Systems: Lessons from OpenJDK — Shubhankar Gambhir

スライド

「プロセス開始時に選ばれる選択(アロケータ、GC)」と「呼び出しを書く場所で選ばれる選択(relaxed か seq_cst か、幅はいくつか)」という2 つの軸が絡むと組み合わせ爆発する、という問題を OpenJDK の GC バリアで論じる。GC バリアはメモリバリアではなくコレクタのための簿記であり、コレクタごとに中身が違う。

実行時コストは解決後の間接呼び出し 1 回で関数ポインタと同等だが、テンプレートの実体化は実在するコストであり、午前 3 時にデバッガでパイプラインをステップ実行する代償は JEP に書かれない。

盗むべき 4 手順は、コンパイル時に既知のものを特定し、変化する軸を分離し(実装の選択は本当に実行時だが意味論はそうではない)、コンパイル時の軸を型システムへ移し、そして単純化する(定数条件によって通らない経路がデッドコードになり、生成コードはむしろ小さくなる)。ただし「呼び出し箇所を間違えると高くつく場合に限る。そうでなければ関数ポインタでほぼ足りる」という限定も置く。

MrDocs: Reliable Reference Documentation for C++ Using the Clang AST — Alan de Freitas

スライド

「関数について一番よく知っているのは、そのシグネチャを書いたまさにその瞬間である」という一文を出発点にする。生成と手書きのドキュメントを、書きやすさ・読みやすさ・AI フレンドリーさ・IDE 統合・検証可能性・ソースとの同期という軸で比較する。

Boost.Beast や Boost.URL は #ifdef BOOST_DOXYGEN でドキュメント用の偽の宣言を書かざるを得ない。そこで「コンパイラをそのまま使えばいいのでは」という発想から、Clang の AST を読む MrDocs が生まれる(既存の Doxygen → XML → docca(XSLT) → Quickbook → BoostBook → HTML というパイプラインを置き換える)。

シンボルコーパスは構造化データとして取り出せるため、ジェネレータはドキュメント生成に限られない。実例として 2 つのバージョンのコーパスを突き合わせ、追加・削除・変更(noexcept の差異など)を列挙して推奨されるバージョン番号の上げ方(major/minor)を出力する破壊的変更レポートのジェネレータを見せる。

API Design is Language Design: Lessons from Go's net/http library for C++ — Tim van Deurzen

スライド

「API 設計は言語設計である」を、SICP・認知科学・Go の net/http の 3 方向から支える発表。SICP の「工学的設計とは言語の層の連なりを作ることだ」を軸に据え、Felienne Hermans『The Programmer's Brain』から混乱の 3 分類(知識の不足・情報の不足・処理能力の不足)を借りる。

SICP の「プログラムは人が読むために書かれるべき」を「人と LLMが読むために」と言い換えるのが本発表の立場。Go の Handler はメソッド 1 つなので「正しい形をした任意の関数が Handler になる」(Rob Pike「インタフェースが大きいほど抽象は弱い」)。

C++ 版をコンセプトで書き起こしつつ、middleware の参照キャプチャのバグ(Go には GC とエスケープ解析がある)や、アプリのエラーを HTTP ステータスへ翻訳する「欠けている層」を指摘する。最後にテーブル駆動テストを「テストの言語」として扱う。


【7. ゲーム・グラフィックス・エミュレーション】

Inside a Small Game Engine: An Architecture Tour in Modern C++ — Elias Farhan

スライド

自作ゲーム『Soup Raiders』を独自の C++ エンジンへ移植し、Nintendo Switch 上で 60Hz・ロード 3 秒未満で動かすという目標を立て、それを達成するまでの記録。Steam では独自エンジンの比率が 2012 年の 71% から 2024 年には 13%(売上本数では 74% から 42%)まで下がっており、その少数派で何が必要かを示す。

構成はコンテナ、インデックスによるポインタ、シーン抽象、レンダリング、アセット読み込み、シリアライズの 6 つ。世代付きインデックス(generational index)を使うと、解放済みスロットへのアクセスが未定義動作ではなく検査の失敗として現れる。シーンでは dirty flag、レンダリングでは背面カリングと視錐台カリングが効果を出したが、いずれも Tracy による計測が先にあった。

C++26 の静的リフレクションはシリアライズのフィールド定義の重複をなくすが、第三者ライブラリの事情までは解決しない(glm::vec3 は無名共用体 3 つでできているため、汎用のウォーカーは cereal が 3 個の float を出す場面で 9 個を出してしまう)。締めは Tyler Glaiel の言葉で、エンジンはゲームなしには何物でもない — エンジンと並行してゲームを作ることが唯一にして破れない規則だという。

In Pursuit of a 6,000 FPS Game Boy Emulator — Tom Tesch

スライド

Game Boy エミュレータの 100 倍高速化の記録(実際には 5,973 FPS だったと冒頭で謝罪から入る)。実機 DMG-001 は 4,194,304 サイクル/秒 ÷ 70,224 サイクル/フレーム = 59.7275 FPS。

Sharp LR35902(SM83、Z80 と Intel 8080 を混ぜた設計で、完全にメモリマップド)の構成と、T-cycle(最高精度)と M-cycle(1 M-cycle = 4 T-cycle)の違いというエミュレーションの基礎から入り、Tetris を題材に最適化を積み上げていく。

なお終盤には、難読化して 1 画面に詰め込んだ Game Boy エミュレータのソースが登場する。

Writing High Performance Parsers Using State Machines — Torben Thaysen

スライド

Torbenx/charge のレキサ・パーサを題材に、状態機械による高性能パーサを解説する。Carbon が掲げた「レキシングとパースを 1,000 万行/秒」という目標に対し、Apple M4 上でレキシング 1,200 万行/秒、パース+レキシング 700 万行/秒を達成した実装が下敷き。

メインループの switch、+ 系トークン(++ += +)の分岐、文字列リテラル、/ とコメントの扱い、識別子テーブルを持つか否か(コンパイラには有用だがハッシュテーブル依存が重く、フォーマッタならキーワード認識で足りる)といった設計判断を積み上げる。

設計上の分岐点は「パース状態ごとにトークン認識を分ける」という選択で、単一の高速レキサより遅くなるが予測可能性が上がり、より多くを扱える。構文検証とフォーマッタには最適で、パースも設計次第で良い、という結論。計測手法も丁寧で、perf stat --control と FIFO を使って反復回数ごとの分岐ミス率を測る仕掛けまで公開する。


【8. テスト・コード品質・保守】

Ensuring Code Quality in the Age of AI: More Code, Less Engineering! — Peter Muldoon

スライド

「AI は大きな機会だが、コード生産を加速しながらそれを検証・保守するエンジニアリングシステムを強化しないと問題が起きる」という立場。

問題の核心はこう置かれる。LLM は次の単語を確率で推測する仕組みであり、知性は創発的で振る舞いは非決定的である。つまり「同じ入力は同じ出力を生み、一度テストすればその後は信頼できる」という従来型エンジニアリングの根本前提が崩れる。AI が自然に向くのはブレインストーミング、高速プロトタイピング、実験、ジャストインタイムの技術調査。

対策は組織の仕組みに落とす — レビューでは「なぜ承認したか」を誰にでも分かる形で残し、テストの存在ではなくテストが何の挙動を証明しているかを見る。リポジトリ側では自己承認の禁止、CI、静的解析、clang-tidy、重複検出、churn 検出。そして成果を測る(マージまでの時間、放棄された PR、無操作の承認、PR サイズ分布、本番障害と復旧時間)。行動喚起は「肉の代理人(meat proxy)になるな」— 読まず理解せず検証せずに AI の出力を転送する人のことで、自分の名前と評判を賭ける判断なら、AI にその根拠を説明させよ、と説く。

Familiar C++ Patterns That Fail at Scale and the Design Shifts That Prevent Them — Divya Chandrasekar, Devpriya Dave

スライド

Bloomberg の長寿システムが 3 度にわたって自分自身を追い越してしまった実話。書かれた当時の前提がスケールとともに崩れた経緯を、main() が返った後にオブジェクトがランタイムへコールバックしてクラッシュするスタックトレースなどで示す。

設計の変更は 3 つ —所有権を明示せよ(読むだけなら所有するな。所有が移るなら移動を可視化せよ)、依存を可視化せよ(ユニットテストに実システムが必要なら依存が隠れている)、不正な状態を表現不能にせよ(呼び出し側が規則を覚えていなければならないなら、インタフェースに符号化せよ)。

最後にエージェント時代への接続がある。AI はこれらの多くを指摘も修正もできるが、意図された契約(誰が所有すべきか、どの状態が本当に妥当か)は既存コードから推測するしかない。だから設計に明示的に符号化するほど、人間もエージェントも推測せずに済む。

Refactoring Techniques and Strategies (a Tale in Three Acts) — Dave Steffen

スライド

Martin Fowler の定義(観測可能な振る舞いを変えずに、理解しやすく変更コストを下げるための内部構造の変更)を確認したうえで進む 4 幕構成(基礎 / 問題 / 作業 / 説得)。

Code Smells を挙げつつ「コードスメルはバグではなく、何かおかしいかもしれないというヒントにすぎない」という限定を置く。目標はバグ修正でも最適化でもなく保守性であり、根底にあるのは単純化・単一責任・抽象度の向上。実務面では移行経路の設計(新 → 旧に転送、オーバーロード集合、[[deprecated]]、最後に削除して「悲鳴を聞き、負傷者を助ける」)を示す。

第 4 幕「売り込み」が本発表の特色で、「何も変わらない作業をなぜやるのか」への答えを用意する。再枠組みとして「プログラムは速くならないが、チームは速くなる」。そして測れないコード品質の代わりに、相関の高いもの(テストカバレッジ=テスト容易性、警告数、循環的複雑度、関数長)を測れ、と説く。

Code You Didn't Write: Understanding Large and Unfamiliar Codebases — Mike Shah, Chris Croft-White

スライド

初日に 100 万行超・20 年物のリポジトリを渡される — 一部はレガシー C++、一部は最新の C++26、そして AI が書いたために誰も完全には理解していない部分が増えつつある。そんな状況で使う技法とツールを、素朴な grep からデバッガ、プロファイラ、タイムトラベルデバッギング、AI 支援の探索まで紹介する。

情報源の信頼度をどこに置くかが全体を貫く — 意図は脆い真実の源であり、振る舞いこそが耐久性のある真実の源である。コメントもドキュメントも設計意図も嘘をつきうるが、実際に走らせて観察した挙動は検証できる。

締めは Douglas Adams の引用 —「何かを本当に理解したければ、誰かに説明してみるのが一番だ。生徒が鈍いほど、より単純な段階へ分解しなければならない。それこそがプログラミングの本質だ。教師は生徒より多くを学ぶ」。

Mock Any Function in C++ Without Changing a Single Line of Code — Ruifeng Zhang, Brian Rudo-Hutt, Ilya Ermakov

スライド

Bloomberg によるオープンソースの BonoboMock の紹介。モンキーパッチングにより、自由関数・静的関数・private メンバ関数までを、プロダクションコードを 1 行も変えずに gMock のモックにする。

DB::lookup() を直接呼ぶ 3 行の関数をテスト可能にするために、従来は依存性注入のためのリファクタリング(22 行の追加、呼び出し側は数えず)が必要だった。実装は関数先頭を数バイトのジャンプで書き換える方式で、Linux と Windows(ラムダを除く)に対応する。

実績は社内の約 1,000 プロジェクト。ただし適用範囲には限定を置く —「テストフレームワークが設計パターンを強制しなくなる分、保守したいコードを書く責任は自分に戻る。できるからといってやってよいとは限らない(たとえば private 関数のモック)」。

Extending Google Test for Statistical Benchmarking, CUDA Profiling, and CI Performance Regression — Kevin Gomez

スライド

「C++ の性能作業はテストスイートの外で生きている」という問題提起 —プロファイラごとに別ワークフロー、GPU にはさらに別の道具、そしてCI はユニットテストを全部走らせてベンチマークは 1 つも走らせないので、遅くなっても何も失敗せず、退行を見つけるのはユーザーになる。

Vernier はこれを Google Test の中に畳み込む。性能テストはただの Google Test のテストでよく、結果がテスト内の値なので時間予算超過やノイズ過多でビルドを落とせる。14 種のプロファイラがフラグ 1 つで同じテストバイナリに走り、「測る → プロファイラに聞く → 指された 1 箇所を直す → また測る」のループが文字列連結で 38 倍、実アプリで 251 倍を生んだ。

そして入力サイズを掃引せよ、1 点を信じるな — 最速の CPU 実装は N に依存して入れ替わり、CPU と GPU の優劣も N とバッチサイズ次第(N=50 では CPU が勝ち、バッチ 32 なら N=2190 で GPU が 7 倍)。

The journey to "/W4 /WX": How hard could it be? — Keith Stockdale

スライド

Rare Ltd(Sea of Thieves)による、巨大コードベースへの最大警告レベル + 警告のエラー化の導入記。規模は Sea of Thieves 29,936 ファイル / 325 万行、エンジン 19,580 ファイル / 347 万行、年間 27 万行以上の増加。

作業量は、/WX の有効化だけで 7,628 件の差分、C4189 の有効化で 254 件、レビュー対象の CL は 154 件。

結論は両論併記。警告は有用である — バグを見つけ、デッドコードを見つけ、不要な処理という性能問題を見つけ、しかもテスト・レビュー・QA・プレイヤーより遥か手前のコンパイル時に見つかるのでレビューも楽になる。ただし全部が割に合うわけではない — 警告の有用性はたいてい発火頻度に比例し、行数が増えるほど苦痛は増し、新しいコンパイラと標準が次々と新しい警告を持ち込む動く標的でもある。だからこそ「プロジェクトが進行してから警告レベルを上げるのは苦痛になる」(Jason Turner)。

AI is UB with Better PR — Andy Soffer, Matt Kulukundis

スライド

「AI は広報がうまい未定義動作である」という主張を 4 幕で展開する、AI とエージェント的開発の思考枠組み。UB の何が悪いのかを「任意に悪いことが起こりうる」「制御できない」「検出できない」の3 つが同時に成り立つことだと整理し(2 つまでなら扱える)、生成 AI も同じ三つ組を持つと論じる。

実例は Replit のエージェントによる本番 DB 削除、matplotlib のメンテナを調査してマージを迫るブログを無断公開した件など。対策は発生の制御(サンドボックス、yolo モードを使わない、静的解析)と検出(自動テスト、サニタイザ、監視)で、「ベストプラクティスは変わっていない。人間のエンジニアのために既に用意すべきものだ」と結ぶ。

第 2 幕では、同じ課題で Sonnet が自力で直そうとして誤ったのに対し Opus は = delete を付けてコンパイルエラーを収穫した例から「大きいモデルは自分で多くをこなすのではなく、ツールをうまく使う」を導き、信頼できるツールへ仕事を持ち上げよと説く。


【9. コンパイラ・言語処理系】

Escaping the AST: A Data-Oriented, Lock-Free Parallel Compiler Architecture — Aditya Kumar

スライド

Vx 言語のコンパイラフロントエンドにおけるデータ指向設計 — フラットな配列、256 ビットの識別子、競合すべきロックのないパイプライン。

モジュール以前の C++ では TU がパース・型検査・コード生成、そしてスケジューリングの単位であり、1 つの巨大 TU が実時間を決めてしまう。C++20 モジュールは再パースを終わらせる真の進歩だが、型依存がビルド順依存に変わり、長い import 連鎖がクリティカルパスになるという新しいコストを持ち込んだ。

Vx の答えは「中央のプールに尋ねない」こと — 型の識別子を内容から計算した 256 ビットのハッシュにすることで、Zig の InternPool のような共有プールもロックも要らなくなる(CI でロックゼロを強制している)。共有カウンタもロックだと見抜き、文字列の採番を prefix sum に置き換えた例がこの方針をよく表す。ただしこれは言語の側が譲ったから成り立つ(Zig の comptime は実行時に型を作るので名前をハッシュできない)という、言語とコンパイラの共同設計の議論として締めくくられる。

Type Punning: the joke is on you, pun intended. Fixing UB of reinterpret_cast! — Lieven de Cock

スライド

Andreas Fertig の記事と静的解析ツールの指摘がきっかけで始まった発表。「自分も知らなかった」「同僚も知らなかった」「専門家ですらキャリア後半で知った」という前置きが、この領域の誤解の広さを示す。

型システムを迂回して同じメモリを別の型として扱う type punning を定義し、2 つの変数の「回顧録」という語り口でオブジェクトのライフタイム(1 つの位置には 1 つのオブジェクトしか生きられない)とアラインメントを導入する。

結論は実務的な行動計画になっている — reinterpret_cast が UB になる理由は数多くあるので、最新の道具に追いつけ(C++20 の std::bit_cast と寿命開始関数、C++23 の std::start_lifetime_as)。grep しにくい C スタイルキャストは -Wold-style-cast で警告にして洗い出し、まず C++ の専用キャストへ置き換えよ。


【10. 数値計算・GPU】

CUDA Tile C++: Practical Automatic Parallelization for the GPU — Ezra Stein

スライド

GPU プログラマが負ってきた責務(ハードウェアスレッドへのデータ割り当て、競合の回避、データ移動とキャッシュの管理、リソースを 100% 使い切ること)をコンパイラに移す CUDA Tile C++ の紹介。プログラマは配列に対する SIMD 的な操作でシングルスレッドのコードを書き、コンパイラがハードウェアへスケジューリングする。

現状の行列積はインラインの PTX アセンブリ(wmma.load / wmma.mma.sync)や CUtensorMap を直に書く羽目になっている。Tile C++ ではタイルとテンソルスパン、パーティションビューで書け、反復空間が既知のループ・入力の非重複宣言・アラインメント宣言が自動並列化と TMA の利用を可能にする。

「Tile C++ は C++ である」— 汎用であり、クラス・共用体・大半の制御フロー・ポインタ演算をそのまま使える。同じファイル内で __global__(SIMT)と __tile_global__ を共存させられる。CUDA Toolkit 13.3 から利用可能(Ampere 以降、C++20 以上、nvcc --enable-tile)。

Scaling Robotics to 200 Developers: Flexible C++ Architectures for Kinematics, Dynamics, Simulation & Control — Carl Saldanha

スライド

Amazon の倉庫でピッキング・仕分けを行うマニピュレーションロボティクス部門による、ロボティクスアルゴリズムのインタフェース設計(最大のプロジェクトでは 100 億個以上の荷物)。焦点は計画(軌道、運動学/動力学、衝突判定)。

インタフェースの目標は 4 つ — 堅牢(インタフェースを決して変えない)、高性能、拡張可能(ライブラリの変化をテストして吸収できる)、モダンで読みやすい C++。自己評価では、制御インタフェースが最も拡張しにくく、性能劣化にも最も弱い。

まとめは「標準は 14 個ある。それでいい」— 抽象の釣り合いが妥当なら、それは良い標準である。短くまとめるなら、バックエンドを差し替え可能にし、計画・制御・シミュレーションで同じ型を使い、そしてアルゴリズムと型をできる限り分離せよ。


セキュリティ・メモリ安全性

Hacking and Securing C++ — Marcell Juhasz

スライド

組み込みデバイス(UART/USB 経由のプロトコル)を題材に、バグから脆弱性、脆弱性から攻撃、そして緩和策までを一通り辿る。

前提として組み込みのメモリモデルと、スタックの解放ではデータは消えないことを丁寧に整理したうえで、固定長バッファへの受信からスタックを破壊し、制御フローを乗っ取り、ROP ガジェットを連ねて execve("/bin/sh") に至るところまで実演する。

対策は 2 段構え。システム側では非実行スタック(MPU)、ASLR(そのためにアドレスをログに出さない)、スタックカナリア、そしてクライアント側の検証を決して信用しないこと。言語側の結論は「モダン C++ を使え」— 要素数を配列に結びつけ(範囲 for、std::array)、memcpy のような無制限なバッファ操作をカプセル化し、動的確保したオブジェクトは必ず初期化する。


その他

The Clocks of C++: Knowing When (and Why) to Use Each One — Sandor Dargo

スライド

計装プロジェクトでタイムスタンプ・クロック型・期間の変換を扱った経験から生まれた発表。C 時代の時間 API の問題(型安全性の欠如、単位の混同、合成不能)を確認し、<chrono> の 3 本柱を解説する。

C++20 のタイムゾーン対応まで進み、zoned_time が時刻と場所を束ねることで夏時間の巻き戻しで曖昧になる時刻や、春先に飛ばされる時刻をライブラリが正しく扱えるようになる実利を示す。

結論は「時間は単なる数ではない」という発想の転換。単調時間か暦時間か、値は飛びうるか、測りたいのか印字したいのかを毎回問え。用途別の早見表(経過時間は steady_clock、暦の論理は system_clock + time_zone)と、system_clock::now() はモックできないので包んで注入せよ — 時間は依存関係として扱えという設計指針で締める。

Pointers to Power: Navigating Organizational Influence — Matt Kulukundis

スライド

SwissTable の作者が自身のキャリアを辿りながら「組織の中でどう影響力を持つか」を語る発表。base への変更を Sanjay に即座に「No」と言われた経験から、相手が何を大事にしているかを理解してから、相手が聞く用意のある議論を提示することを学ぶ。

意見が割れた社内ライブラリの調停で編み出したのが文書による意思決定の型 —選択肢(「何もしない」を必ず含める)と長所短所を網羅し、関係者それぞれに自分の名前で段落を書かせる。

規則は「意見は重み付けであって、事実を持ち込んではいけない」。会議ではなく文書にするのは、会議では立場がその場で硬直するから。SwissTable では反対意見を「賛成するために何が必要かの説明」と捉え、影響を単一の数字ではなく悲観・楽観の幅で示して信用を得た。

一方、Noogler 向け提案が実績を積んでもなお通らなかった失敗も語られる。


【11. Lightning Talks】

operator<> — Ben Deane

スライド

「高階メタプログラミングの平等を求める夢」という副題のライトニングトーク。現在のメタプログラミングは 20 年前の高階関数と同じ場所にいると指摘する —テンプレートは型ではなく、型とテンプレートを一様に渡せず、テンプレートを返せず、部分適用も合成もできない。

20 年前の答えはライブラリ(Boost.Bind、Boost.Lambda)で、それでは足りないと分かって C++11 でラムダ式を言語に入れた。同じことをメタ世界でやろう、というのが operator<> の提案で、メタ値とメタ関数を一様に渡せ、メタ関数を返せ、合成と部分適用が単純になる。

「リフレクションがあるのでは」への答えが、この提案の位置づけを決めている —リフレクションは言語が許すものしか反映できない。だからテンプレートメタプログラミングを補完するのであって、置き換えはしない。易しくはするが、単純にはしない。

Much Ado About Null — Keith Stockdale

スライド

TestNotNullOrAbort というヌルチェックヘルパの設計を段階的に改善していくライトニングトーク。

メッセージ付きとなしの 2 つのオーバーロードがあると、ポインタを渡すべき場所に文字列を渡すコードが通ってしまう。これを concepts(requires (!TIsCharTypeV<PointerType>))で塞ぐ。だがチェックを通った後も「このポインタは本当にまだ非 null か」という疑問は残る。auto* const で再代入を防ぐ案は複雑さを持ち込むので退け、参照を返す設計に到達する。

実装は難しくなるが、static_assert 群で戻り値型(int32* → int32&、void* → void)を検証でき、その帰結としてnullptr を渡すコードがコンパイルエラーになる。

Eight Bytes From a Six-Byte Field — Mohammad Al-Hanoosh

スライド

6 バイトのアドレスを比較するのに、8 バイト幅で読むのと 4+2 バイトに分けて読むのとどちらが速いかを測ったライトニングトーク。8 バイト版は ((x ^ y) << 16) == 0 で余分な 2 バイトを落とし、分割版は ((x ^ y) | (u ^ v)) == 0 でちょうど 6 バイトしか読まない。どちらも分岐を持たない。

結果は 8 バイト版 0.35ns/ペア、分割版 0.81ns/ペア、memcmp 6 バイト 0.58ns/ペア。理由はワイドループでは 2 つの 16 バイトロードが 2 ペア分をまとめて取り込み、punpckhqdq/punpcklqdq でグループ化できるのに対し、分割版は小片を集めるためにより多くのロードと unpack を必要とする(8 ペアあたりの命令数で比較して示される)。

結論は一行 —「読むバイト数を減らしても、この比較は速くならなかった」。

書籍レビュー『C++でつくる ゲーム、対戦AI、CG、オーディオ、電子工作、競プロ、CI/CD、言語仕様』

amzn.to

gihyo.jp

本を送っていただきました。ありがとうございます。

編集の小吹さんや、著者の鈴木さんは、私が主催する勉強会C++ MIXにちょくちょく来られるのですが、うしろの方でなにか話してるなと思っていたら、この本を書いていたのですね。

雑誌のようにいろいろなC++での開発分野について学べる本

C++は多くの分野で使われています。本書で扱われているゲーム、CG、オーディオ、電子工作、競プロももちろんのこと、サーバー、金融、車、HPC、シミュレーションなど、挙げたらきりがないくらい、さまざまな分野で使われています。そして、勉強会やカンファレンスといった場では、そういったさまざまな分野でC++を使っている方々が集まり、相互のコミュニケーションによって分野間の異なる常識を共有し、理解を深め、他分野の優れた工夫を自分の分野にももってきたりして、開発が成長していっています。

本書はまさにそのような本です。

C++は入門書で文法と標準ライブラリの使い方を学んでも、それだけでは開発には足りません。分野ごとに使われるフレームワークやライブラリ、SDKといったものを使いこなすことで開発していきます。本書は、C++の入門を終えたあとに読む本としては最適だと感じました。

学生がC++の入門書を読んだとして、自分がまだどの分野に興味があるかまだわからないけど、ざっくりでいいのでいろんな分野でどういう開発をするのかが知りたい、といったときに読むのにとてもちょうどいい本だと思います。

やはり専門分野ごとに本は分かれてしまうので、興味をもてるかどうかわからない段階で専門分野の本を買うのは、なかなかハードルが高いと思います。しかし、本書を読んで興味をもったら専門分野の本を買ってみる、という道筋が作れるので、興味をもつ機会を作るのにとてもよいと思います。

各分野の専門家が単なる概要ではない深い知識や分野ごとの工夫を教えてくれる本

こういった、多彩な話題を扱う本は、興味をもってもらう第一歩なのもあって、内容が概要レベルでおわってしまいがちです。しかしこの本は、各分野の専門家が深い経験と知識をもとに、かなり深いところまで教えてくれます。

この分野にはこういった特徴があり、こういったところで工夫していかないといけない、というのがわかると、単にものを作るだけでなく、開発者のがんばりどころがよくわかります。プロクオリティの製品を作るためにはこういったことを考えないといけないというのが把握できるので、とても助かります。

これは手元に一冊は置いておいて、ちょっと別な分野に興味がでたら読む、というのを何度もできてよいです。

私も『プログラミングの魔導書』という雑誌ライクな本を以前に作っていました。いろんなことに興味をもつ機会を作れる雑誌という媒体が好きで、しかし雑誌だと初心者寄りになってしまうというのがあり、中級レベル以上向けにいろんな話題が載った本として作ったのが『プログラミングの魔導書』でした。本書もまさにそういった本としてとても良い取り組みだと思いました。

ぜひシリーズ化してほしい

本書ではさまざまな分野のプロフェッショナルが、自分たちの分野のたのしさ、がんばりどころを教えてくれましたが、C++が活躍する分野はまだまだあります。紹介しきれていない分野のためにも、本書はぜひシリーズ化していってもらいたいです。

また、本書の著者陣が書かれた記事もたいへん勉強になりましたので、この著者陣が書いた各分野の専門書もぜひとも読みたいです!

たのしい本をありがとうございました〜。著者のみなさま、お疲れさまでした。

cpprefjp最初の完成のお知らせ - C++29の現在の仕様案まで追いつきました

2011年から始めたC++日本語リファレンスサイトcpprefjpの取り組みですが、C++29の最新ワーキングドラフトの状態まで、ほぼ完全に追従しました。
15年かけてようやく最新に追いつきました。

全体では7,405ページになりました。
本一冊を300ページとすると25冊分の文量になります。1ページあたりの情報量も多いので、実際にはその数倍になるでしょう。

この15年、多くの方に助けていただいて、なんとかここまで来れました。まずは大きな感謝をさせてください。ありがとうございます。

cpprefjpの取り組み

cpprefjpは、C++11がでるタイミングで、最新の言語事情を包括的に解説しているWebサイトや書籍がなかったことに危機感をもって始めたプロジェクトです。
特徴的な機能の解説などは、個々人のブログなどで行われていましたが、クラスのすべてのメンバ関数の解説やサンプルコードなどは個人で解説しきるのはむずかしいと考えていました。仮に私個人でがんばってそれらを包括的に解説したとしても、今度は間違い指摘を管理するのがむずかしいと考え、最初から共同執筆できるプロジェクトとして始めました。

またcpprefjpをはじめた動機として、私がBoostなどのC++の先進的な機能を仕事で使おうとすると、「日本語の情報が少ないから問題が起きたときに怖いので採用できない」と言われ、仕事で新しい機能が使えないということがありました。
母国語の情報を広めることで、私のように新しい機能を使いたいけど使えない開発者が不遇な思いをしなくて済むようにしたいと考えました。

cpprefjpが扱う範囲

cpprefjpでは標準ライブラリのリファレンスを作ることが第一目標でした。そのあとに言語機能のアップデートを追加で書くようになりましたが、扱う範囲としてはその2つです。

とくに注力しているのは、ライブラリの仕様だけ読んでもどう使えばいいかわからない、という問題に対処するために、サンプルコードを充実させました。基本的にほぼすべての機能に対してひとつ以上のサンプルコードをつけるようにしています。
「ほぼ」と書いているからには例外はあるのですが、たとえばデストラクタなどはサンプルコードを書いても有用なものにするのはむずかしいので、そういったものは対象外にしています。

途方もない目標のために習慣化して執筆する日々

cpprefjpは標準ライブラリのすべての関数・クラスを解説するということで、途方もない目標を立ててしまいました。
その膨大な作業をやり切るためには情熱だけでは保たないので、習慣化する必要がありました。
私もこれでもクリエイターの端くれなので、やりたいこと、作りたいものというのは、ぽんぽん思い浮かんでくるのですが、そういったのも押し殺して、自分のやりたいことや横道に逸れることを頭から追い出して、ひたすらに空き時間にcpprefjpを書き続けました。
仕事に疲れたら休憩がてらcpprefjpを書き、仕事がおわったらcpprefjpを書き、という生活をずっと続けてきました。
自分のやりたいことを押し殺しすぎて、一時期は何に対しても何も感じなくなってしまい、心が死んでしまっていたこともありました。過度な習慣化は心を壊してしまうのだということも学びました。

最近はプライベートとの両立バランスもとれてきています。cpprefjpだけでなくほかの創作にも手を出したりして、4コママンガを作ったり、作曲したりもしています。自分の心や家庭、創作意欲とのバランスをとりながら、無理なく今後もcpprefjpを続けていきたいです。

note.com

note.com

人集めについて

cpprefjpは最初のころは、みんなでこれを完成させよう!と声がけして執筆メンバーを集めました。しかし最近は声がけはとくにしていません。cpprefjpは開発者を支援するサービスですので、まずはみなさんに自分の作りたいものに注力してほしいという気持ちが強くなったからです。

しかしそれでも、こちらから声がけしていないにも関わらず、「普段からcpprefjpにはお世話になっているので貢献したい」ということで参加いただく方がちょくちょくおられて、大変感謝しています。

追加でほしい人材としては、営業や広報の経験がある方に入っていただけると、とても心強いですね。cpprefjpを活用していただいている企業というのは多くあると思うのでスポンサーシップへの参加などの相談をしにいけたらと思うのですが、私がそういったことに向いてない & 作業でいそがしいというのがあるので、行き届いてないですね。

さまざまな失敗

cpprefjpを作るにあたっては、さまざまな失敗にぶち当たりました。

  • 最初に大目標だけ立ててしまい、中期目標がないので大きな達成感がずっとない。数カ月単位でも達成感を感じやすいような目標設計にすればよかったと思っています
  • 私のマネジメント力・伝え方の不足で、参加してくださった方につらい思いをさせてしまったことがあります。いまでも申し訳なく思っています
  • 最初はGoogle Sitesではじめたので、Wiki + メーリングリストだと管理に限界がありました。一括修正もGoogle App ScriptのAPI制限でやりにくかったです。後にGitHub Pagesに移行しましたが、英断だったと思います
  • 大目標だけだったこともあり、何度も燃え尽きてしまい、数カ月作業が進まないこともありました。燃え尽きるとそのままやめてしまったりということもあると思いますので、心の管理も大事だと思いました

私もマネージャー職というわけでもないので、必要に迫られて完成のためのタスクマネジメントやコミュニケーションを学びました。向いているわけでもないとは思っていますが、完成の責任や、フィードバックを受けさまざまな視点からの改善を取り入れるため、向いていないなりにマネジメントに取り組みました。
マネジメントと同時に執筆もするし、ツール整備などもします。いまだとプレイングマネージャーと言われたりするようですが、執筆もがんばらないといけないけど、新たなツールの仕組みがないと書けないものもあるからツール改善に取り組まないといけないし、いろいろな質問や相談にも対応しないといけない、のように、執筆をがんばれば完成するというものでもなくて大変でした。
人が増えるたびに新たな視点がもたらされはしますが、明文化したルールがないと動きにくいこともできてきたので、そのたびルールを整備したり、書き間違えたらチェックに引っかかるようCIを整備したりもしました。

それでも、私を含めて157人の貢献や建設的な提案・問題提起のおかげで (Google Sites時代を含めたらさらに何名かいらっしゃいます)、なんとかここまでこれました。

スポンサーシップの取り組み

あるとき、私が結婚して私生活も大事にしないといけない時期になったときに思ったのが、無償ボランティアだとcpprefjpを継続するのに不安がある、ということでした。
そう思っていたときに、ある方からcpprefjpのスポンサーになりたいというお話がありました。 無償の作業は、私自身の気持ち次第でいくらでも後回しにできてしまいます。スポンサーシップをはじめてお金をいただくことで、作業を継続する強制力を作れるのではないかと考えました。

また、作業はできないけどお金で貢献したい、という気持ちにもスポンサーシップという制度によって応えられると考えました。

スポンサーシップの制度設計はなかなかむずかしかったです。
最初はGitHub Sponsorsを検討しました。しかし、会社としてスポンサーになるためには別なサービスの方がうれしい、と指摘を受け、Open Collectiveというサービスを使うことになりました。

また、貢献度に応じてスポンサー様方からいただいたお金を分配する仕組みづくりもしました。GitHubリポジトリでは、コミット数や行数などは統計情報として取得できるのですが、作業者としてはそれを指標にすることには納得感がありませんでした。大きいコミットをする人もいれば小さいコミットをする人もいますし、一括置換のようにツールで簡単に大量の修正をすることもあれば、数文字だけど重要な変更というのもあります。
それを踏まえて、どういった貢献にどれくらいのポイントが付くのか、というのをまとめ、1年分のコミットを追いかけて貢献内容をすべてチェックしてポイント換算するようにしました。

スポンサーシップの取り組みは、以前記事を書きましたので、そちらも参照ください。

levtech.jp

cpprefjpのスポンサーシップの取り組み、Open Collectiveへのリンクやスポンサーの掲載は、トップページに記載してあります。

cpprefjp.github.io

また、私個人へのGitHub Sponsorsもありますので、よろしければそちらも応援いただけるとうれしいです。

github.com

AIによる変化

ChatGPTがでたときから、調べ物や提案文書の要約・質問などで助かっていました。

Claude Codeがでたときには、cpprefjpの執筆に実際に使いものになることがわかって、かなり助かるようになりました。しかし、コーディングと違ってcpprefjpは自然言語のリポジトリなこともあり、Claude Codeのようなコーディングエージェントを使っても、いろいろとむずかしい部分はありました。

むずかしい部分の大きなところは、大きな作業を依頼するとけっこうサボるということです。ライブラリ仕様が部分的にしか書かれなかったり、ほかのページのコピペみたいな内容になってしまったり、参照リンクだけ追加して仕様はなにも更新されなかったりします。また、スキルでcpprefjpのルールを事細かに指示しても、常に完全に守られるわけではなく、たびたび何かのルールを守らない記述をします。

こういった問題への対処として、「執筆後はサブエージェントに、スキルのルールが守られているか、修正内容が正しいか、提案文書と実際に取り込まれた内容は異なる場合があるので最新のワーキングドラフトも合わせてチェックして」などと指示してレビューさせるといった工夫をすると、かなり品質が向上するようになりました。

ただ、これらをやったとしても、まだまだ手放しにcpprefjpの執筆を任せることはできない状況です。1年後にはさらによくなると思いますが、自然言語ベースの作業はまだむずかしいなと感じています。

しかしそういった問題はあるにせよ、執筆速度は大幅に向上しました。すべてを手書きしていた頃は、たとえば<filesystem>ライブラリは190ページほどありますが、執筆に半年以上かかりました。しかし最近書いた<meta>ライブラリの240ページ超は、Claude Codeの助けもあって数日で書き上げることができました。

また、Defect Report (仕様バグの細かな修正) への対応もC++11〜C++29で数千件ありましたが、これも1週間ほどで対応できました。常に人手不足のプロジェクトなので大変助かっています。

Defect Reportへの対応をしたこともあり、標準ライブラリのリファレンスの方は、ほぼ全ての変更の経緯をcpprefjpから追跡できるようになりました。ChatGPTなどで仕様の経緯などを調査するときにも、この情報が役立つことになると思います。

AIに書かせた文章はAIっぽくなる、というのがネガティブな話として最近よくあると思いますが、cpprefjpでのAI活用では、いまのところその点は問題にならなそうだな、と思っています。なにもないところからAIに文章を書かせるとAIっぽい文章になりますが、Claude Codeをはじめたときからcpprefjpには5,000ページを超える既存の文章があり、「周りの書き方を参考にして書いて」と指示していることもあり、既存ページの書き方を踏襲してもらっています。文書の内容・書き方もすべてチェックして改善は都度指示しています。

いまはまだ手放しでは任せきれないAIですが、工夫を続けることで、膨大な作業も少ない人数でこなせるようになる大きな希望になりました。

自分の主軸をどこに置くか

cpprefjpを執筆している間、私は仕事でC++だけを触っているわけではなく、C#、TypeScript、Rust、Go、Elixir、Pythonなどなど、多くの言語を使ってきました。新しく有望な言語がでてきてそちらに主軸を移す人たちを見て、それも正しい選択だと思いつつ、仕事ではいろいろな言語を使いながらも自分はC++が主軸であり続けていいのか、という葛藤は常にあります。

しかし、cpprefjpというある意味インフラになっている取り組みに携わっている以上は、流行りに乗ってあちこちに主軸を移すのではなく、なにがいつ流行るかもわからないですし、この場所を守り続けるのも大事なんだろうな、とも思っています。

cpprefjpに足りないもの、これからのこと

cpprefjpは一旦の作業の節目を迎え、現状の作業範囲だとC++29以降のアップデートに追従していけばいい、という状況にはなりました。 しかし、cpprefjpは言語側が弱いと多くの方から指摘を受けているように、言語機能はアップデートの記載のみでリファレンスがなかったりして、できることはまだまだあると考えています。

これからどこを強化していくか、メンバーたちと相談しながら、さらにより良いものにしていきたいです。

おわりに

cpprefjpには多くの方が参加されていることもあり、ここで個別にお礼を書くのはむずかしいのですが、関わっていただいたすべての方に感謝しております。

スポンサーシップに参加いただいた方々にも大変感謝しております。無料で使えるサービスにも関わらず応援していただき、ありがとうございます。cpprefjpの継続的な活動のための、大きな支えになっています。

cpprefjpは、誤字ひとつの指摘でも、「ここの説明だとわかりにくい」という指摘でも、小さなことでもIssueをいただけると、さらによくなっていけます。みなさんの気づきがありましたらどんどん教えていただけると助かります。

私たちのこういった経験や取り組みが、どなたかの一助になれば幸いです。これからもがんばっていきます。

最近のC++のハッシュマップ事情

C++標準ライブラリのハッシュマップであるstd::unordered_mapより2〜3倍速いハッシュマップ実装が、外部ライブラリとしていくつもあります。なぜ標準のものが遅いのか、どれを選べばいいのか、乗り換えるときに何に気をつけるべきかをまとめていきます。

なぜstd::unordered_mapは (ほかと比べて) 遅いと言われるのか

std::unordered_mapの実装を速くすることに限界があるのは、仕様がクローズドアドレッシング (closed addressing、実装としては分離連鎖法) 方式を前提にした設計・仕様になっているからです。とくに、以下の2つが方式を限定する要因になっています。

  • 参照安定性の要求 - 変更操作やリハッシュをしても参照とポインタが (削除した要素以外) 無効にならないという要求がある。そのために、要素をひとつずつヒープに確保してポインタでつなぐノードベースの実装が事実上強制される。検索のたびにポインタをたどることになり、キャッシュミスが1回余分に発生する
  • bucket()やbegin(n)といったバケットAPI - 「バケットの配列があり、その中に複数の要素が入る」という内部構造を外部向けインタフェースにしているため、別方式への変更に制限がある

オープンアドレッシングとSwiss Table

最近のハッシュマップ実装ではオープンアドレッシング方式がとられていて、それを使うと2〜3倍は高速になります。代わりに参照安定性がなくなります。

注:ちなみにクローズドアドレッシングとはキーに対応するスロットの位置 (番地という) が確定していて動かない方式、オープンアドレッシングは番地が動いてもよい方式です。

オープンアドレッシングにもロビンフッド法やホップスコッチ法などいくつかの手法がありますが、現在の主流はGoogleのSwiss Tableで、Abseilライブラリのabsl::flat_hash_mapで使われています。これは値の配列とは別に「1スロットあたり1バイトのメタデータ配列」を持ち、そこにハッシュ値の一部を入れておいて、SIMD命令で16スロット分を一度に照合するという方式です。ポインタを辿らず、比較の回数も大きく減ります。

同じ発想のものとして、Boostライブラリのboost::unordered_flat_mapと、MetaのFollyライブラリのF14があります。どちらもSwiss Tableの派生ではなく独立した設計ですが、「メタデータをSIMDで一括照合する」という部分は共通しています。

どれを使うのがよいか

ハッシュマップのベンチマークはいろいろあるようですが、総合的には現在のところ、以下の3つが最速または最良の選択肢のようです。

コンテナ 方式 向いている用途
boost::unordered_flat_map SIMDグループプローブ 汎用。挿入・削除が混ざっても安定
absl::flat_hash_map Swiss Table 汎用。大規模データの検索に強い
ankerl::unordered_dense バケット配列+密な値コンテナ 走査が多い。削除が少ない

BoostやAbseilは、別の用途でもそれらのライブラリを使っているのであれば、そのコンテナを使用するのがよさそうです。

ハッシュマップだけ高速なのがほしいのであれば、ankerl::unordered_denseはそのコンテナだけの単一ヘッダのライブラリなので、導入しやすそうです。個人リポジトリです。

ankerl::unordered_denseの位置づけ

Swiss Table系が「メタデータ配列 + 値配列」なのに対し、ankerl::unordered_denseは「バケット配列 + 密な値コンテナ」という、オープンアドレッシングではありますが別の方式をとっています。値をstd::vectorに隙間なく詰めておき、検索用のバケット配列にはそのvectorの添字を持たせる、という構成です。

性能の特徴は次のとおりです。

  • 走査は最速 - イテレータが実質std::vectorのイテレータなので、空きスロットの読み飛ばしが原理的に発生しない
  • 検索はほかの2つと同程度
  • 挿入と削除はほかの2つより遅い - とくに削除は、vectorの末尾要素を穴に移動させたうえで、その要素を指していたバケットを探し直す必要があるため、検索2回分+ムーブ1回分のコストがかかる

なので、削除をあまりせず、検索や走査を何度も何度も高速に実行するのに向いているようです。

移行時の注意点

オープンアドレッシング方式のコンテナに移行する際の最大の注意点は、参照安定性がなくなるということです。無効化のタイミングはコンテナによって違うので、既存コードのコンテナを差し替えたい場合には注意してください。

コンテナ 挿入 (リハッシュ時) 削除
std::unordered_map イテレータのみ無効になる 削除した要素のみ無効になる
absl::flat_hash_map
boost::unordered_flat_map
すべて無効になる 削除した要素のみ無効になる
ankerl::unordered_dense すべて無効になる すべて無効になる

ankerl::unordered_denseだけは単一要素の削除でも全要素が無効になります。穴の埋め方が「末尾の要素を移動させる」方式なので、ほかの要素のアドレスと走査順序が変わるためです。

とくに次のようなコードは、差し替えると静かに壊れます。

  • 要素への参照やポインタを変数に持ったまま、別の要素を挿入している
  • 走査しながら要素を追加している

参照安定性がどうしても必要な場合は、boost::unordered_node_map、absl::node_hash_map、folly::F14NodeMapといったノード版が用意されています。これらは要素のアドレスを安定させたまま、最新のプローブ方式による高速化の恩恵を受けられます。

参照

3命令のCPU処理で済む高速なうるう年判定アルゴリズム

要約

この短いコードで、西暦102499年までのうるう年を判定できます。このマジックナンバーはz3というSMTソルバーで見つけ出されました。

bool is_leap_year_fast(uint32_t y) {
    return ((y * 1073750999) & 3221352463) <= 126976;
}

はじめに

GCC (libstdc++) のうるう年判定のアルゴリズムが、以下のように変更になりました (commit)。

constexpr bool is_leap() const noexcept
{
-    return (_M_y & (_M_y % 25 == 0 ? 15 : 3)) == 0;
+    const auto __y = static_cast<uint32_t>(_M_y) + 32800u;
+    return ((__y * 1073750999u) & 3221352463u) <= 126976u;
}

謎のマジックナンバーが使われていますが、これについて、コミット内に解説記事へのリンクがあります。

hueffner.de

以下のコードを使用することで、西暦0年から102499年までの任意の年 y が閏年かどうかを、わずか約3命令のCPU処理で判定できます:

bool is_leap_year_fast(uint32_t y) {
    return ((y * 1073750999) & 3221352463) <= 126976;
}

この記事では、この謎のマジックナンバーがどのように発見されたのかと、GCCの変更判断について調査した結果をレポートします。

今回の最適化手法

うるう年判定は、単純には剰余を使った処理で実装できます。

bool is_leap_year(uint32_t y) {
    if ((y % 4) != 0) return false;
    if ((y % 100) != 0) return true;
    if ((y % 400) == 0) return true;
    return false;
}

これは分岐予測に当たりやすい値に対して速くなり、そうでない値で遅くなるという特徴があります。

この従来の判定方法を最適化していった先がGCCの旧実装のマスク方式になります。それらの従来方式の最適化の話は、元記事から辿れますので、興味がある方はそちらから追いかけてください。

横道に逸れた話

ちなみに今回のうるう年判定では「先発グレゴリオ暦」が使われていて、1582年に導入されたグレゴリオ暦から過去に延長した方式で0年までを扱っています。

グレゴリオ暦の前のユリウス暦では「うるう年の定義は4年に一度」、グレゴリオ暦は多すぎるうるう年を減らすため、「うるう年は4年に一度、ただし100で割り切れる年は平年、ただし400で割り切れる年はうるう年」となっています。今回はユリウス暦の年は対象外として考えられています。

さて、今回の最適化の出発点は、すべての入力に対して正確な結果を返す、というのを諦めて範囲を狭めることで、うるう年判定をより高速化できるのではないか、ということです。実際、PythonやC#では西暦9999年までしかサポートされていないので、整数型で表現できる全ての年を対象にする必要はなさそうです。実際、C++標準ライブラリでの年の値の範囲は[-32767, 32767]なので、判定可能な範囲は狭くて問題なさそうです。

((y * f) & m) <= tという式でうるう年判定をしようと考えられましたが、パラメータの組み合わせを総当たりするには領域がとても広かったため、今回はz3というビットベクトル制約をサポートしたSMTソルバーを使用し、先の式を満たすうるう年判定のパラメータを見つけ出す、という手法が使われました。SMTソルバーというのは「この条件を全て満たす値はあるか」を自動で探索するプログラムです。

github.com

具体的なz3を使用したパラメータ探索のコードは以下になります。

import z3

# 探索関係の変数宣言
# BITS : ビット数
# f, m, t : 探したい定数 (乗数、マスク、閾値)
# y : 年。これだけForAllで束縛する
BITS = 32
f, m, t, y = z3.BitVecs('f m t y', BITS)

# 仕様。何が正しいのかの答えを出す関数
def target(y):
    return z3.And((y & 3) == 0, z3.Or(z3.URem(y, 25) != 0, (y & 15) == 0))

# 候補。どんな式で置き換えたいか
# ULEは符号なしの<=比較 (unsigned less than or equal)
def candidate(x):
    return z3.ULE((x * f) & m, t)

solver = z3.Solver()
solver.add(z3.ForAll(y, z3.Implies(z3.ULE(y, 400), # 今回の上限年数
                                   candidate(y) == target(y)))) # 候補と仕様が完全一致すること

if solver.check() == z3.sat:
    print(f'found solution: {solver.model()}')
else:
    print('no solution found')

ここでは例として400年分の年の値で、「うるう年を((y * f) & m) <= tという式で判定できるfとmとtの値が存在するか」という問いを、ソルバーに渡しています。これを実行すると数秒で結果が得られます (400年程度だと式を満たす解は複数ありえる)。ちなみに400年というのは、グレゴリオ暦のうるう年規則が一巡する年数です。

f = 268875817
m = 807400109
t = 2080768

作者はこの範囲を拡大していろいろ工夫してパラメータを探索した結果として (工夫の詳細は不明)、以下のパラメータを発見しました (上限として西暦102499年まで扱える)。

f = 1073750999u
m = 3221352463u
t = 126976u

これによって、以下の高速版のうるう年判定関数ができあがりました。

bool is_leap_year_fast(uint32_t y) {
    const uint32_t f = 1073750999u;
    const uint32_t m = 3221352463u;
    const uint32_t t = 126976u;
    return ((y * f) & m) <= t;
}

作者の記事では、見つかったパラメータ値の考察がいろいろ行われていますので、興味がある方はそちらを読んでください。

私としては、今回の学びとしては、この手法を使うことで高速化できる関数がいろいろありそうだな、ということでした。マジックナンバーが使われたコードは人間が読むには適していませんが、ソルバーで得られたパラメータなので有効範囲内では正しいことが信じられます。ただ、有効範囲がコードから読み取りにくいこともあってソフトウェアテストもしておくと安心して使えるかと思います (有効範囲のチェックをコードで入れると遅くなる)。

似たようなマジックナンバーの例としては、逆平方根 (平方根の逆数) があると思いますが、研究や職人技で見つけられていた高速化のためのマジックナンバーの発見を、だれでもできるような環境が整ったのだな、というのが今回の大きな発見・気づきでした。

z3を使った最適化は、コンパイラ開発などではすでに定着しつつあるようです。実際、LLVMにはツールとしてあちこちで使われているようです。例:

  • Alive2 - AliveToolkit 最適化前後の意味論的な等価性の検査。レビュー作業に実際に組み込まれている
  • Souper - google LLVMの中間最適化器が見落としている最適化機会を自動発見する

GCCはなぜ実装を置き換えたのか

GCCのメーリングリストでは、今回の変更のパッチが提出されたことで、いくつかの議論がありました (メーリングリストの投稿へのリンク)。

  • GCCがループをベクトル化する場合には旧アルゴリズムの方が20%速いが、自動ベクトル化を切ると新アルゴリズムの方が2倍以上速い。ループの中でこの関数が呼ばれることは稀なので、実運用ではGCCはこの関数をベクトル化しないであろうから、新アルゴリズムの方がよい
  • 旧アルゴリズムはスループットでは優位だが、新アルゴリズムの方がレイテンシーで優位。並列で大量にこの関数を呼ぶわけではないので、レイテンシーの方が重要

旧アルゴリズムがスループット (大量に呼ぶ場合のパフォーマンス) で優位なのは、16ビット演算で済むために自動ベクトル化された際にSIMDレジスタに詰め込める数が多いために高速動作し、新アルゴリズムの方は32ビット演算なので自動ベクトル化される状況では不利ということのようです。

今回の高速化で重要なのは、うるう年判定をループ中で大量に実行するのを高速化したいわけではなく、いろいろな処理を行っている中の一部として使われるこの処理を高速化しておきたい、ということのようです。

この件での学びとしては、標準ライブラリというのはこういう関数でも高速化する必要があるのだなというのと (標準ライブラリがなんらかのボトルネックになってはいけない、みたいなこと)、高速化にも方針がいくつかあるのだな、ということでした。

早期returnについて補足

最初の方に、剰余を使った早期returnは、分岐予測が当たったときに速く、そうでないときに遅いというのがあると書きました。

これはつまり、4で割り切れない年は75%あり、そういった入力が圧倒的に多いので早期returnがあったほうが速いだろう、というもので、それは実際そのとおりです。

その上で早期returnバージョンと分岐なしバージョンの、よいところ・よくないところをまとめてみました。

早期return 分岐なし
よいところ 4で割り切れない入力に対しては最速 (GCCだと今回のバージョンより最高で15%速い)。
読みやすい。
入力によらず一定速度。
自動ベクトル化の恩恵を受けられる。
よくないところ 経路によっては2倍程度遅くなる。
自動ベクトル化の恩恵を受けられない。
全経路を常に計算する。

4で割り切れない入力に対して早期returnが15%速かったという話の測定条件ですが、1. ベクトル化なし、2. 4で割り切れない入力、分岐予測が当たった、という3条件でした。

自動ベクトル化については、今回の設計選択で重要視されなかったポイントではありますが、いちおうまとめました。

早期returnの場合には分岐をマスクに変換する自動ベクトル化もがんばれば可能ですが、コンパイラによってたいして速くはならなかったり、むしろ遅くなったりしました。分岐なしでのGCCの新しいバージョンの場合は、16ビットから32ビットに変わったことで自動ベクトル化時にむしろ遅くなってはいます。試しにz3でやってみたら16ビットの場合には「解なし」になってしまったので、式を変えたりなどでまた別な工夫が必要かもしれません。

どちらも一長一短はあるので、どれが最適解というわけではなさそうですので、設計選択ですね。

ゲーム開発で乱数生成としてメルセンヌ・ツイスターの代わりにPhiloxを使う場合に考えること

C++26から新たに擬似乱数生成器としてPhiloxというアルゴリズムが入ります。

32ビット版はstd::philox4x32、64ビット版はstd::philox4x64です。

Philoxはカウンターベースの乱数生成器というもので、key (seed) とcounter (4次元の添字) の組み合わせによってランダムアクセスで乱数を生成します。

既存のメルセンヌ・ツイスターを使ったコードを書き換えるほどではない

既存のstd::mt19937を使ったコードを書き換える必要はとくにないですが、新規にゲームを作る場合には、std::mt19937の代替としてPhiloxは有力な選択肢になります。

外部ライブラリや自前実装としてすでに別な擬似乱数生成器を使っているならそれはそれでいいのですが、Philoxを使う場合には設計が変わるので、今回の記事では主に設計の話をしようと思います。

Philoxの特徴

Philoxのまず基本的な特徴は以下です。

  • 周期として必要十分
    • メルセンヌ・ツイスターは219937 - 1の長い周期をもつことが特徴でしたが、Philoxは2130になります。
  • サイズ
    • メルセンヌ・ツイスターは624個の32ビット値からなる大きな状態をもちますが、Philoxの状態はカウンターが4ワード、キーが2ワード、出力バッファ4ワードと小さく済みます

基本的な性能として必要十分なのはいいとして、特徴的なのは、シード + 4個の整数の組み合わせ (128ビットで位置を指定) で、乱数を一発で求めることができる点です。

従来は以下のように、ひとつひとつ乱数を生成していましたが、

random rnd{seed};
rnd();
rnd();
rnd();

Philoxでは従来の方法に加えて、以下のような生成ができます。

philox4x32 rnd{seed};
rnd.set_counter({a, b, c, d});
rnd();

ゲーム開発における乱数生成の特徴

ゲーム開発の乱数の用途はいろいろありますが、ひとまずエフェクトのような用途は外して考えるとして、リプレイ対応のために再現性が重要だったり、マルチプレイのために複数クライアントに同じ乱数列を生成させたり、ユーザーに次の乱数を予測させないことも大事、というのがあります。

お金が絡むガチャとかはサーバーで生成させますが、スタンドアローンで動くゲームでユーザーの予測を困難にする要求はそれなりにあります (ポケモンで乱数調整のためのツールがあったりしますが、開発者の望む結果としてそうなってるわけではないはず)。

それに加えて、最近のゲーム機は複数コアをもっているために並行処理が当たりまえになっていたり、コルーチンや非同期処理が多用されるようにもなっていますので、その状況でもリプレイやマルチプレイで同じ乱数列を生成できる必要もあるかと思います。 そのため、呼び出しタイミングに依存せずに、用途ごとに同じ乱数列が生成できるとうれしくて、Philoxだとそういうケースに向いています。

実際の使い方としてひとつの案

void UpdateCharacter(int tick, int chara_id) {
  std::philox4x32 rnd{seed}; // シードは事前に計算しておく
  for (int i = 0; i < N; i++) {
    rnd.set_counter({
      static_cast<int>(RandomKind::Character), // 用途 (enum値)
      chara_id, // エンティティのID
      tick, // フレームカウンタ (ゲーム/シーンが開始してからのカウンター)
      i // ループカウンタ。用途ごとの何回目の生成か
    });
    int rnd_value = rnd();
  }
}

この設計なら、非同期・並行に関係なく用途ごとに、そのフレームの何回目の生成かを渡す感じで乱数を生成できます。

set_counter (位置指定) と rnd() (乱数生成) は、計算量としてどちらも定数時間です。つまり任意の位置の乱数にランダムアクセスできる感じです。

乱数生成器の変数は、メンバ変数ではなくローカル変数でもちます。それによって、スレッド間で状態を共有せず、位置を指定してその場で乱数を生成します。

カウンターの種類は4つに限定されていますが、それで足りない場合には、フレームカウンタ以外の要素を、上位16ビットと下位16ビットに分けて指定することになるかと思います。 64ビット版なら上位32ビットと下位32ビットが使えるので、足りるかと思います。

標準の分布クラスとの組み合わせに注意

クロスプラットフォームでマルチプレイをするような場合には、std::uniform_int_distributionやstd::normal_distributionとの組み合わせに注意が必要です。

これらのクラスは、結果の値を生成するために、乱数を何回生成するかが規定されておらず、ビルド環境・標準ライブラリ実装によって異なる結果になる可能性があります。 なので可能なら自前実装したほうがいいとは思いますが、その上で、分布クラスとの組み合わせは、基本的には専用の用途をまるまる確保したほうがいいかもしれません。

void GenerateMonsterParameter(int tick, int monster_id) {
  std::philox4x32 rnd{seed};
  rnd.set_counter({
    static_cast<int>(RandomKind::MonsterParam),
    monster_id,
    tick,
    0 // 最下位のカウンターは0にしておく
  });

  MyNormalDistribution<float> dist_hp{100.0f, 150.0f};
  int hp = static_cast<int>(dist_hp(rnd));

  MyNormalDistribution<float> dist_attack{200.0f, 300.0f};
  int attack = static_cast<int>(dist_attack(rnd));
}

ここではHPと攻撃力を同じMonsterParam列挙子から生成していますが、ここに指定できるのはまるまる32ビット分あるので、より細かくMonsterParamHP, MonsterParamAttackとかで分けてもいいかもしれません。

この設計でカバーできないこと

シード値の変数をメモリ上で読まれたりしたら、次の乱数を予測されるかもしれません。その場合はSteam・PCプラットフォーム上だと多少危ないかもしれません。しかし、お金が絡む処理はサーバーでやるので、本当に守りたいものはクライアントで生成しなければ大丈夫でしょう。

従来のやり方だと、いろんな用途でごちゃまぜに乱数生成してるから予測が困難というところもあったかと思いますが、今回のやり方だとシード値として日時とかじゃなくハードウェア乱数 (std::random_device) を使う程度の予測困難さになっています。そのあたりは、非同期・並行での扱いやすさとのトレードオフかなと思います。

書評『ゆるやかに学ぶC言語』 - プログラミング初心者向けC言語入門書

amzn.to

書籍を送っていただきました。講談社サイエンティフィク様、著者の鈴木さんありがとうございます。

著者の鈴木 遼さん (Reputeless) さんは、以前からC++コミュニティで精力的に活動されている方で、ゲーム開発などで使えるC++フレームワークSiv3Dを開発されていたり、CEDECやC++ MIXで発表されていたりもします。

普段は早稲田大学などでプログラミング教育に取り組まれていて、本書もそういった彼の経験が大きく反映されたものとなっています。

本書はC23というC言語の最新規格を使った、C言語の入門書です。C言語は古いバージョンでの解説文はこれまでにたくさん出版されていますが、新しめの書き方を紹介した書籍はなかなかありません。最新のプログラミング言語は、初心者がとっつきやすいようにガードレールが整備され、理解しやすく書きやすいものとなっているので、プログラミング初心者の方にやさしいものとなっています。

また、解説の仕方についてもたいへんすばらしく、教育のお仕事をされているだけあって、数々のつまづきポイントをカバーされています。よくある書き間違いを紹介し、それのなにがどう間違っていて、どこに気をつけてコードを書く必要があるのかを、とても丁寧に解説されています。

プログラミング初心者の方はやはり、いろんなところでつまづきます。 本に書いてあるとおりに打ち込んでも、コンパイルエラーになったり、うまく動かなかったりします。 そういったときに立ち止まってしまい、インターネットで調べたりChatGPTに聞いたりすることになりますが、横道に逸れている間にほかのことに没頭してしまったり、ゲームをはじめてしまったり、本とは違うことが書かれていてよくわからなくて挫折したり、といったことが起こります。 本書は、つまづきポイントが多くカバーされていることにより、本当にこの本を一冊まるまる読むだけで学びきれてしまうようになっているのがすごいところです。

C言語は1970年代からある古い言語ではありますが、ハードウェアに近い言語としていまでも様々な場所で使われる現役の言語です。 これからも学ぶ価値が高いC言語を学ぶ書籍として、とくに最新のC言語を学ぶ書籍としては、本書が最適なものになりそうです。

C++ MIX #18 開催案内

cppmix.connpass.com

次回のC++ MIXは7月10日 (金) に開催します。 みなさまのご参加をお待ちしております。

なお、7月とその次は、私は不参加になる予定です。ほかのスタッフがこれまで通り運営しますので、安心してご参加くださいませ。

C++ポケットリファレンス 第5版 (C++23) の増刷が決まりました

私たちの共著書『C++ポケットリファレンス』の第5版 (C++23版) について、増刷が決まりました。

C++11版を2013年に出版してから12年が経ち、いまでも改訂、増刷が続けられていることに感謝です!