結論
C++26で導入される静的Reflection(P2996)を使うと、構造体のメンバ一覧をコンパイル時に取得して、シリアライズ処理を自動生成できます。まずは一番シンプルな例を見てみましょう。
#include <meta>
#include <print>
struct Point
{
int x;
int y;
float z;
};
int main()
{
// 全てのメンバ情報をコンパイル時に取得
constexpr auto members = std::meta::nonstatic_data_members_of(^^Point);
std::println("Point has {} members", members.size());
// template for でメンバ名を列挙
template for (constexpr auto m : members)
{
std::println(" - {}", std::meta::identifier_of(m));
}
}
これをGCC 16以降で -std=c++26 -freflection フラグ付きでコンパイルすると、こんな出力が得られます。
Point has 3 members
- x
- y
- z
静的Reflectionの3大原則:
①コンパイル時のみ動作する
②ランタイムオーバーヘッドはゼロ
③型安全。
マクロやSFINAEを使った天才的なテンプレートメタプログラミングの時代は、そろそろ終わりです。
C++26 静的Reflectionとは何か
Reflectionとは、プログラムが自分自身の構造(型・メンバ・関数など)を検査したり操作したりする能力のことです。C++26以前は、こんな方法で型情報にアクセスしていました。
- X-Macroパターン(メンバ一覧をマクロで二重管理する地獄)
- SFINAEと非常に複雑なテンプレートメタプログラミング
- 外部コードジェネレータ(QtのMOCなど)
C++26の静的Reflection(P2996)では、^^ 演算子で型や変数やメンバのリフレクション情報を取得し、[: :](スプライサ)でリフレクション情報をコードに戻せます。「コードを書くコード」が、ついに標準化されたわけです。
基本構文:^^ 演算子と [: :] スプライサ
^^ 演算子:コードをリフレクション情報に変換する
^^ を型や変数やメンバの前に置くと、std::meta::info という型の値が手に入ります。
#include <meta>
struct Person
{
std::string name;
int age;
};
// 型のリフレクション
constexpr auto person_info = ^^Person;
// メンバのリフレクション
constexpr auto name_info = ^^Person::name;
constexpr auto age_info = ^^Person::age;
// 式のリフレクション
int global_var = 42;
constexpr auto var_info = ^^global_var;
[: :] スプライサ:リフレクション情報をコードに戻す
リフレクション情報から元のコードを復元するには、[: :] を使います。
// 型情報をコードに戻す
using MyType = [:^^Person:]; // Person と同じ意味
// メンバアクセスに使う
Person p{"Alice", 30};
int val = p.[:^^Person::age:]; // p.age と同じ(30になる)
// 条件付きで型を選ぶこともできる
using Bigger = [: sizeof(long) > sizeof(int) ? ^^long : ^^int :];
標準クエリ関数一覧
<meta> ヘッダには便利なクエリ関数がたくさん用意されています。
| 関数 | 戻り値 | 説明 |
|---|---|---|
identifier_of(info) |
string_view |
名前を取得する |
type_of(info) |
info |
メンバの型を取得する |
nonstatic_data_members_of(info) |
range<info> |
全データメンバを取得する |
member_functions_of(info) |
range<info> |
全メンバ関数を取得する |
enumerators_of(info) |
range<info> |
列挙子一覧を取得する |
parameters_of(info) |
range<info> |
関数のパラメータ一覧を取得する |
base_classes_of(info) |
range<info> |
基底クラス一覧を取得する |
実践1:JSONシリアライザを自動生成する
「全ての構造体に to_json() を書く」という退屈な作業、もうやめましょう。Reflectionを使えばたったこれだけです。
template<typename T>
std::string to_json(const T& obj)
{
std::ostringstream oss;
oss << "{";
bool first = true;
template for (constexpr auto member : std::meta::nonstatic_data_members_of(^^T))
{
if (!first)
{
oss << ", ";
}
first = false;
// キー:メンバ名
oss << "\"" << std::meta::identifier_of(member) << "\": ";
// 値:型に応じて出力を変える
using MemberType = decltype(obj.[:member:]);
if constexpr (std::is_same_v<MemberType, std::string>)
{
oss << "\"" << obj.[:member:] << "\"";
}
else
{
oss << obj.[:member:];
}
}
oss << "}";
return oss.str();
}
// 実際に使ってみる
struct Config
{
std::string host;
int port;
bool use_tls;
};
int main()
{
Config cfg{"localhost", 8080, true};
std::println("{}", to_json(cfg));
// 出力: {"host": "localhost", "port": 8080, "use_tls": 1}
}
template for はC++26のexpansion statement(P1306)という機能で、コンパイル時にループを展開します。従来の std::apply と比べると、はるかに読みやすいですね。
実践2:SQLクエリを自動生成する(簡易ORM)
template<typename T>
std::string generate_insert(const T& obj)
{
std::string table = std::meta::identifier_of(^^T);
std::string cols = "";
std::string values = "";
bool first = true;
template for (constexpr auto m : std::meta::nonstatic_data_members_of(^^T))
{
if (!first)
{
cols += ", ";
values += ", ";
}
first = false;
cols += std::meta::identifier_of(m);
values += std::format("'{}'", obj.[:m:]);
}
return std::format("INSERT INTO {} ({}) VALUES ({})", table, cols, values);
}
struct Product
{
int id;
std::string name;
double price;
};
int main()
{
Product p{1, "Laptop", 999.99};
std::println("{}", generate_insert(p));
// INSERT INTO Product (id, name, price) VALUES ('1', 'Laptop', '999.99')
}
メンバを1つ追加しても、insert文を生成するコードは一切修正する必要がありません。これがReflectionの本当の価値です。
実践3:Enum→文字列変換(ゼロオーバーヘッド)
enumの値から文字列への変換、これまでマクロか手動マッピングが必要でしたが、C++26ならこんなにシンプルです。
template<typename T>
requires std::is_enum_v<T>
consteval auto make_enum_table() noexcept
{
constexpr auto enumerators = std::meta::enumerators_of(^^T);
std::array<std::pair<T, std::string_view>, enumerators.size()> table{};
std::size_t i = 0;
template for (constexpr auto item : enumerators)
{
table[i] = { [:item:], std::meta::identifier_of(item) };
i++;
}
return table;
}
template<typename T>
requires std::is_enum_v<T>
constexpr std::string_view enum_to_string(T value) noexcept
{
constexpr auto table = make_enum_table<T>();
template for (constexpr auto [v, name] : table)
{
if (v == value)
{
return name;
}
}
return "unknown";
}
enum class Color { Red, Green, Blue };
int main()
{
auto c = Color::Green;
std::println("{}", enum_to_string(c)); // Green
}
実行時のメモリ確保もテーブル生成コストもゼロです。全てコンパイル時に確定し、バイナリの .rodata セクションに配置されます。
契約プログラミング(Contracts)
C++26では契約プログラミング(P2900)も導入されます。関数の事前条件や事後条件を言語レベルで書けるようになります。
int divide(int dividend, int divisor)
[[pre: divisor != 0]]
[[post r: r * divisor == dividend]]
{
return dividend / divisor;
}
void set_volume(int level)
[[pre: 0 <= level && level <= 100]]
[[post: get_volume() == level]]
{
volume_ = level;
}
契約はビルドモードで制御できます。プロダクションではチェックをオフにしてオーバーヘッドをゼロにし、テストやデバッグビルドで厳格にチェックする、といった使い方が可能です。
コンパイラの対応状況
| コンパイラ | Reflection対応 | コンパイルフラグ |
|---|---|---|
| GCC 16+ | 実験的対応(P2996の大部分) | -std=c++26 -freflection |
| Clang 19+ | 部分的対応 | -std=c++26 -freflection |
| MSVC | 開発中 | 未定 |
GCC 16が一番進んでいます。すぐに試したい方は Compiler Explorer で GCC 16 を選んで -std=c++26 -freflection を指定すれば、ブラウザからそのまま実行できます。
【広告】ドメインを取得してC++プロジェクトを公開するならお名前.com。格安で独自ドメインを取得しましょう。![]()
まとめ
C++26の静的Reflectionは、C++11以来最大の転換点だと言っても過言ではありません。「テンプレートメタプログラミングで天才的なコードを書く」時代から、「コンパイラに型情報を直接問い合わせてコードを生成する」時代へのパラダイムシフトです。今後10年かけて、C++のエコシステムを大きく変えていくでしょう。
^^演算子と[: :]スプライサが基本構文です- 自動シリアライズ、ORM、enum変換が数行で書けます
- オーバーヘッドゼロ、型安全、コンパイル時検査
- Contractsと組み合わせれば、より堅牢なコードベースに
- GCC 16 +
-freflectionで今すぐ試せます
【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!![]()

コメント