JSON形式の設定ファイルがあるとします。以下のようなものです:

{ "width": 1920, "height": 1080, "fullscreen": true,
  "title": "My Game", "volume": 0.8 }

通常、このファイルをプログラムと一緒に配布し、起動時に開いて読み込み、パースします。決して変わらないデータのために多くの作業が必要になります。もしファイルがビルド時に固定されているとしたら?コンパイラがそれを読み込んでパースし、結果を直接実行ファイルに定数として焼き込むことはできるでしょうか?

C++26では、答えは「はい」です。GCCコンパイラの最新版(16)ですぐに利用できる新しい要素が2つ必要です。

  1. コンパイル時にファイルをプログラムに取り込むための#embed
  2. simdjsonのような静的リフレクションをサポートするソフトウェアライブラリ。

どこまで実現できるかを見てみましょう。

新しい#embedディレクティブはファイルの内容を読み込み、バイト値のカンマ区切りリストに展開します。コンパイル時にdata.jsonファイルを読み込み、定数として保持するには次のように書きます:

constexpr const char json_data[] = {
#embed "data.json"
    , 0
};

constexprを使うのは、コンパイラが定数評価中にこれらのバイトを検査できるようにするためです。末尾の, 0は単にヌル終端子を追加し、配列を通常のC文字列として扱えるようにします。

実行時の入出力は一切ありません。バイトはプログラムの一部です。

しかし、埋め込まれたバイトだけではまだ十分に役立ちません。本当に欲しいのは型付きのC++オブジェクトです。この例での対象型は次の設定構造体です:

struct Window {
  int         width;
  int         height;
  bool        fullscreen;
  std::string title;
  double      volume;
};

JSONからこのような構造体を埋める従来の方法は、手作業でフィールドごとに1行ずつ書くことです: "width"を読み込んでwidthに格納し、"height"を読み込んでheightに格納し、以下同様です。これは退屈な作業です。また、起動時に実行されるため、不正なファイルはユーザーが発見する実行時エラーになります。

最近のバージョンのsimdjsonは、C++26の静的リフレクションを使ってJSONをコンパイル時にパースできます。エントリポイントはsimdjson::compile_time::parse_jsonで、まだ少し魔法のように感じることを行います: JSONを読み込み、見つかったキーから構造体型を合成します。

#define SIMDJSON_STATIC_REFLECTION 1
#include "simdjson.h"
constexpr const char json_data[] = {
#embed "data.json"
    , 0
};
constexpr auto window = simdjson::compile_time::parse_json<json_data>();

変数windowは完全にコンパイラによって計算された値です。その型はドキュメントから生成されます: widthheight(どちらも64ビット整数)、bool型のfullscreendouble型のvolume、およびtitleを持ちます。以降はwindow.widthのように書けば、通常のフィールドのように振る舞います。

パースが本当にコンパイル時に行われたことをどうやって知るのでしょうか?プログラムが存在する前にコンパイラがチェックしなければならない結果についてアサートできるからです:

static_assert(window.width      == 1920);
static_assert(window.height     == 1080);
static_assert(window.fullscreen == true);

JSONを破損させた場合(括弧を削除する、trueを綴り間違える、末尾のカンマを残すなど)、プログラムはコンパイルされなくなり、エラーはparse_json行を指します。壊れたファイルは他の人の起動時ではなく、自分のマシンでのビルド時に捕捉されます。

windowは本物のコンパイル時定数であるため、それに対する計算も定数になります。次の関数を考えてみましょう:

int  screen_area()   { return window.width * window.height; }

-O3でコンパイルすると、乗算もフィールドアクセスも、パースも残りません — 答えだけが即値として残ります(ここではmacBook上):

screen_area:    mov  w0, #0xa400        // 0x1fa400 = 2073600
                movk w0, #0x1f, lsl #16
                ret

JSONはバイナリから消えました。コンパイラによって一度だけ読み込まれパースされ、残るのは数値2073600だけです。

静的リフレクションは非常に新しいため、GCC 16でビルドする場合は-std=c++26 -freflectionフラグを渡す必要があります: -freflectionフラグはコンパイル時リフレクションを有効にするために必要です。また、simdjson.hをインポートする前にsimdjsonマクロSIMDJSON_STATIC_REFLECTION=1を設定する必要があります。これは一時的な安全策です。

これらの例を再現するためのソースコードはこちらで入手できます

参考: P2996 — C++26のためのリフレクション および simdjsonライブラリ

クレジット: simdjsonの実装はFrancisco Geiman Thiesenとの共同作業です。