yohhoyの日記

技術的メモをしていきたい日記

C++ Reflection(P2996R10)

次期C++2c(C++26)標準規格に向けて検討が進んでいる リフレクション(reflection) についてメモ。本記事の内容は提案文書 P2996R10 に基づく。

2025-07-18追記:2025年6月会合にてP2996R13[言語コア機能とライブラリ]、および一連の関連提案P3560R2[エラーハンドリング]、(PDF)P3096R12[関数パラメータ]、P3394R4アノテーション]、P3491R3[静的データ定義](→id:yohhoy:20250629)、P1306R5[展開文](→id:yohhoy:20250228)などが採択された。*1

要約:

  • 新しい演算子^^[::]の組
  • 新しい構文
    • リフレクト式:^^識別子^^型名
    • スプライサ構文:[:r:]
    • constevalブロック宣言:consteval { statement(s) }
  • 新しい標準ヘッダ<meta>
    • std::meta::info
    • 名前空間std::meta以下のリフレクション・メタ関数群
  • C++エンティティが持つあらゆる静的情報の問合せをサポート
    • 例1:列挙子の名前を取得(→id:yohhoy:20250228)、エンティティのソースコード位置source_locationを取得。
    • 例2:クラス型の全メンバ情報を取得、テンプレート引数情報を取得、エイリアス元情報を取得。
  • テンプレートパラメータの置換(substitute)操作をサポート
    • SFINAE(Substitution Failure Is Not An Error)エミュレーション動作を行える。と思う。
  • リフレクション情報からの集成体(aggregate)定義をサポート
    • クラスや共用体(union)の型情報をコンパイル時に組み立てるメタ・プログラミング。
  • テンプレート・メタ関数(<type_traits>)に対応するリフレクション・メタ関数(<meta>)
    • 例1:is_pointer_v<T>bool meta::is_pointer_type(info)
    • 例2:remove_cvref_t<T>info meta::remove_cvref(info)

リフレクション演算子

C++エンティティに対してリフレクション演算子(reflection operator)^^を適用したリフレクト式(reflect-expression)は、std::meta::info型のリフレクション値(reflection value; reflection)に評価される。リフレクション演算子^^オペランドには下記C++エンティティを指定できる。

  • 名前空間エイリアス(namespace alias)
  • 名前空間(namespace)
    • ^^:: == グローバル名前空間のリフレクション値
  • コンセプト(concept)
  • クラステンプレート(class template)
  • 関数テンプレート(function template)
  • 変数テンプレート(primary variable template)
  • エイリアステンプレート(alias template)
  • 型(type)
  • エイリアス(type alias)
  • 関数(function)
  • 変数(variable)
  • 構造化束縛(structured binding)
  • 列挙子(enumerator)
  • 非staticデータメンバ(non-static data member)

次のC++エンティティには適用できない。

  • 非型テンプレートパラメータ(non-type template parameter) *2
  • パック・インデクス式(pack-index-expression) *3

リフレクション値

フレクション値を表現するstd::meta::info型の特徴:

  • コンパイル時にのみ利用可能な型(consteval-only type)として提供される。
  • デフォルト構築はヌル・リフレクション(null reflection)値となる。
  • リフレクション値の等値比較(==, !=)をサポートする。
  • 単一の不透明(opaque)型として設計される。
    • <meta>ヘッダ:リフレクション値の問合せ(query)を行うリフレクション・メタ関数群を提供。
  • 新しいプライマリ型カテゴリ(→id:yohhoy:20141122)として追加される。
    • <type_traits>ヘッダ:std::is_reflectionテンプレート・メタ関数を追加。
  • リフレクション演算子では直接取得できない特殊なリフレクション値も存在する。
    • 無名ビットフィールド(unnamed bit-field):無名ビットフィールドを含むクラス型Tからmembers_of(^^T)[n]
    • データメンバ記述(data member description):data_member_spec(^^T, {.name="m1"})
    • 直接基底クラス関係(direct base class relationship):public/protected/private継承関係。派生クラス型Dからbases_of(^^D)[0]
#include <meta>
// 結果型は全て std::meta::info
constexpr auto r1 = ^^int;  // 型
constexpr auto r2 = ^^std::string;  // 型エイリアス
constexpr auto r3 = ^^std::malloc;  // 関数
constexpr auto r4 = ^^std::vector;  // クラステンプレート
constexpr auto r5 = ^^std::same_as;  // コンセプト
constexpr auto r6 = ^^std::linalg;  // 名前空間

// リフレクション・メタ関数は名前空間 std::meta に属する
// 引数型 std::meta::info からのADLによりスコープ指定は省略可能
static_assert( is_type(r1) );
static_assert( is_type(r2) && is_type_alias(r2) );
static_assert( is_function(r3) );
static_assert( is_template(r4) && is_class_template(r4) );
static_assert( is_template(r5) && is_concept(r5) );
static_assert( is_namespace(r6) );

ノート:属性(attribute)に対するリフレクションはP3385にて検討中*4属性と構文が似たユーザ定義アノテーション(annotation)導入はP3394にて検討中*5。関数パラメータ(function parameter)はP3096にて検討中*6

スプライサ構文

std::meta::info型のリフレクション値rからC++エンティティを得る、スプライス指定子(splice-specifier)[:r:]が新しい構文要素として追加される。

  • スプライス式(splice-expression):[:r:]template [:r:]で定数値を得る。
    • rがテンプレートを表すときtemplateキーワードが必要。
    • rがコンセプトを表すときはスプライス不可。*7
  • スプライス・型指定子(splice-type-specifier):[:r:]typename [:r:]で型を指定。
    • 型が自明に要求されるコンテキストではtypenameキーワード省略可能。
  • スプライス・スコープ指定子(splice-scope-specifier):[:r:]::名前空間を指定。
constexpr auto r_type = ^^int;
typename [:r_type:] x;  // int x;
         [:r_type:] y;  // int y;

namespace NS { void fn(); }
constexpr auto r_ns = ^^NS;
[:r_ns:]::fn();  // NS::fn();

template<int N> void fn();
constexpr int C = 42;
constexpr auto r_tfn = ^^fn;
constexpr auto r_var = ^^C;
template [:r_tfn:]<[:r_var:]>();  // f<42>();

集成体・共用体の定義

std::meta::define_aggregate関数とconstevalブロック宣言(consteval-block-declaration)を組み合わせて、クラス型や共用体型に対してリフレクション値からデータメンバを定義できる。std::meta::data_member_spec関数+std::meta::data_member_options*8によりデータメンバ記述・リフレクション値を生成し、定義対象型とデータメンバ記述リストを指定する。

#include <meta>

struct Point;
consteval {
  std::meta::define_aggregate(^^Point, {
    data_member_spec(^^float, {.name = "x"}),
    data_member_spec(^^float, {.name = "y"})
  });
}
// struct Point { float x; float y; };と等価
// P2996R10, §4.8 A Simple Tuple Type
#include <meta>

template<typename... Ts> struct Tuple {
  struct storage;
  consteval {
    // storageクラスに(無名)データメンバを追加する
    define_aggregate(^^storage, {data_member_spec(^^Ts)...});
  }
  storage data;

  Tuple(const Ts& ...vs): data{vs...} {}
};

// Tuple<int, char>から下記クラス(相当)を定義
struct Tuple<int, char>::storage {
  int  _u0;
  char _u1;
};
// _uN は無名データメンバのためメンバ名でのアクセス不可
// nonstatic_data_members_of関数とスプライス式を利用し
// constexpr auto ctx = std::meta::access_context::current();
// data.[:nonstatic_data_members_of(^^storage, ctx)[N]:] とする

2025-07-29追記:P2996R8時点で無名データメンバはNGに変更されており、上記§4.8引用コードのままではill-formedとなる*9。メンバ変数名を明示指定するか、data_member_spec(^^Ts, {.name="_"})*10に修正する必要がある。

関連URL

*1:https://herbsutter.com/2025/06/21/trip-report-june-2025-iso-c-standards-meeting-sofia-bulgaria/

*2:非型テンプレートパラメータ V に対して std::meta::reflect_value(V) とすればリフレクション値を得られる。2025-05-21追記:P2996R12で std::meta::reflect_constant に名称変更。

*3:C++2c言語仕様への採択が決定している args...[n] 式。(PDF)P2662R3 参照。

*4:https://github.com/cplusplus/papers/issues/2042

*5:https://github.com/cplusplus/papers/issues/2074

*6:https://github.com/cplusplus/papers/issues/1764

*7:リフレクション・メタ関数 std::meta::can_substitute を用いて、あるコンセプトを用いた制約が満たされる(satisfied)か否かを判定可能。例:can_substitute(^^std::integral, {^^int});

*8:データメンバの name, alignment, bit_width, no_unique_address を制御可能。

*9:P2996R13, [meta.reflection.define.aggregate]: "Constant When: if options.name does not contain a value, then options.bit_width contains a value;"

*10:https://cpprefjp.github.io/lang/cpp26/nice_placeholder_with_no_name.html