Reflection@C++26

-- Views

June 19, 26

スライド概要

profile-image

きりんさんがすきです。でもC++さんのほうがもーっとすきです。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

実⾏時に評価を 許さないReflection @C++�� �/��

2.

C++��の新機能: 静的リフレクション • コンパイル時にC++の様々なエンティティの情報を取得する 機能 • 実⾏時には使⽤できない • 限定的なクラス⽣成 �/��

3.

エンティティの情報を取得する namespace ns { struct type {}; void func(); int var = 0; } // 各エンティティの種類をチェックする // ^^はリフレクションオブジェクト(以下、単にinfoとする)を // 取得するための演算⼦ static_assert(meta::is_namespace(^^ns)); // 名前空間 static_assert(meta::is_type(^^ns::type)); // 型 static_assert(meta::is_variable(^^ns::var)); // 変数 // varはnsに属している static_assert(meta::parent_of(^^ns::var) == ^^ns); // 名前空間nsの最初のメンバーはtype constexpr info first = meta::members_of( ^^ns, meta::access_context::unchecked())[0]; static_assert(first == ^^ns::type); �/��

4.

アノテーション • エンティティにユーザー定義の情報を添付する機能 • 添付した情報は他と同様にinfoから取得できる struct [[=123]] type {}; static constexpr info type_anno0 = meta::annotations_of(^^type)[0]; static_assert(meta::extract<int>(type_anno0) == 123); �/��

5.

実⾏時には使⽤できない int main() { // info r1 = ^^int; // エラー 実⾏時の変数にinfoを使った constexpr info r2 = ^^int; // OK // 以下のdisplay〜(r2)も実⾏時ではなくコンパイル時に評価されている std::println("{}", meta::display_string_of(r2)); } �/��

6.

限定的なクラス⽣成 • リフレクション関数を使ってクラスの中⾝を⽣成できる。 • 前⽅宣⾔のみのクラスや、実体化されていないクラステンプ レート識別⼦に対してのみ使⽤可能。 ◦ 既存クラスへのメンバー追加はできない。 • ⽣成できるのはデータメンバーのみ。 ◦ メンバー関数を定義したり基底クラスを指定したりは できない。 �/��

7.
[beta]
限定的なクラス⽣成(普通のクラス)
struct point;
consteval {
meta::define_aggregate(^^point, {
meta::data_member_spec(^^int, { .name = "x" }),
meta::data_member_spec(^^int, { .name = "y" }),
});
}
int main() {
point p = { .x = 10, .y = 20 };
std::println("{}, {}", p.x, p.y);
}

�/��

8.
[beta]
限定的なクラス⽣成(テンプレート特殊化)
template<typename T>
struct t { int a; };
consteval {
meta::define_aggregate(^^t<int>, {
meta::data_member_spec(^^int, { .name = "b" }),
});
}
int main() {
t<char> tc = { .a = 10 };
std::println("{}", tc.a);
t<int> ti = { .b = 10 };
std::println("{}", ti.b);
}

�/��

9.

限定的なクラス⽣成 • リフレクション関数を使ってクラスの中⾝を⽣成できる。 • 前⽅宣⾔のみのクラスや、実体化されていないクラステンプ レート識別⼦に対してのみ使⽤可能。 ◦ 既存クラスへのメンバー追加はできない。 • ⽣成できるのはデータメンバーのみ。 ◦ メンバー関数を定義したり基底クラスを指定したりは できない。 �/��

10.

せやろか? ��/��

11.

メンバー関数⽣成を 許していないReflection を出し抜く@C++�� ��/��

12.
[beta]
関数オブジェクトデータメンバー
struct print_t {
static void operator()() {
std::println("{}", "hello");
}
};
struct hoge;
consteval {
meta::define_aggregate(^^hoge, {
meta::data_member_spec(^^print_t, { .name = "print" })
});
}
int main() {
hoge x;
x.print();
}

なんかそれっぽいことはできる。

��/��

13.
[beta]
関数オブジェクトデータメンバー
にthisを認識させる
メンバー関数なら this で他のメンバーにもアクセスしたい。
template<typename Self, std::size_t I>
struct print_t {
void operator()() {
std::println("{}", self().msg);
}
Self const & self() const {
return *(Self *)((char *)this - offset());
}
static consteval std::size_t offset() {
info mem = meta::nonstatic_data_members_of(
^^Self, meta::access_context::unchecked())[I];
return meta::offset_of(mem).bytes;
}
};

��/��

14.
[beta]
関数オブジェクトデータメンバー
にthisを認識させる
メンバー関数なら this で他のメンバーにもアクセスしたい。
struct hoge;
consteval {
meta::define_aggregate(^^hoge, {
meta::data_member_spec(^^std::string, { .name = "msg" }),
meta::data_member_spec(^^print_t<hoge, 1>, { .name = "print" }),
});
}
int main() {
hoge x { .msg = "quack" };
x.print();
}

��/��

15.
[beta]
関数を外から指定できるようにする
template<typename Self, auto F, std::size_t I>
struct memfun_t {
template<typename FnSelf, typename ...Args>
decltype(auto) operator()(this FnSelf && fn_self, Args && ...args) {
return F(
std::forward_like<FnSelf>(fn_self.self()),
std::forward<Args>(args)...);
}
Self & self() const {
return *(Self *)((char *)this - offset());
}
static consteval std::size_t offset() {
info m = meta::nonstatic_data_members_of(
^^Self, meta::access_context::unchecked())[I];
return meta::offset_of(m).bytes;
}
};

��/��

16.
[beta]
関数を外から指定できるようにする
struct hoge;
consteval {
constexpr auto f = [] (auto && self) {
std::println("{}", self.msg);
};
meta::define_aggregate(^^hoge, {
meta::data_member_spec(^^std::string, { .name = "msg" }),
meta::data_member_spec(^^memfun_t<hoge, f, 1>, { .name = "print" }),
});
}
int main() {
hoge x = { .msg = "quack" };
x.print();
}

��/��

17.

許せないReflection の挙動 @C++�� ��/��

18.

vectorを返してくる関数 • std::vector はコンパイル時に評価可能である。 • が、constexpr変数にはできない(※)のでよくあるケースで 使い勝⼿が悪い。 ◦ ※細かい話を置いとくと「できない」と⾔ってよい。 ◦ 使い勝⼿が悪いので std::define_static_array という関数 が提供されている。 ◦ あとGCCの実装もなんか怪レい。 ��/��

19.
[beta]
vectorを返してくる関数
メンバー⼀覧を出力する
template<info E>
void print_members() {
template for (constexpr info mem:
meta::members_of(E, meta::access_context::unchecked()))
{ std::println("{}", meta::display_string_of(mem)); }
}

エラー
• この形式の template for は meta::members_of の戻り値を constexpr
変数で受ける。
◦ 戻り値の型は std::vector<info>
• new で取ったメモリを抱えるオブジェクトは constexpr 変数に
保存できない。
��/��

20.
[beta]
vectorを返してくる関数
メンバー⼀覧を出力する
template<info E>
void print_members() {
template for (constexpr info mem:
std::define_static_array(
meta::members_of(E, meta::access_context::unchecked())))
{ std::println("{}", meta::display_string_of(mem)); }
}

通る
• std::define_static_array 関数は範囲から静的な配列を作り出
し、その配列を指す std::span を返す。 new で確保したメモリ
は⼀切ない。
• std::span はポインタを持っているだけ。 new は⼀切ない。
��/��

21.
[beta]
vectorを返してくる関数
メンバー⼀覧を出力する
template<info E>
void print_members() {
constexpr auto mems = std::define_static_array(
meta::members_of(E, meta::access_context::unchecked()));
template for (constexpr info mem: mems)
{ std::println("{}", meta::display_string_of(mem)); }
}

なんかエラー
• GC⼦⽈く「反復する対象のmemsがローカル変数だと、同
じ引数でも評価の度にmemsのアドレスが変わるからよくな
い。memsをstaticにしなさい」。
• しかし規格を読んでもその旨を読み取れなかった。
��/��

22.
[beta]
vectorを返してくる関数
メンバー⼀覧を出力する
template<info E>
void print_members() {
constexpr auto mems = std::define_static_array(
meta::members_of(E, meta::access_context::unchecked()));
template for (constexpr info mem: auto(mems))
{ std::println("{}", meta::display_string_of(mem)); }
}

なんか通る
• ⼀時オブジェクトならアドレスはどうでもいいらしい
• 本当にこんな動作で合っているの⋯⋯?

��/��

23.
[beta]
vectorを返してくる関数
他の⽅法で解決する
define_static_array ではなく reflect_constant_array を使う。この

関数は配列に対する info を返すので、スプライスで取り出して構
造化束縛で受け、↓の形式で展開する。
template<info E>
void print_members() {
constexpr auto [...mems] =
[:meta::reflect_constant_array(
meta::members_of(E, meta::access_context::unchecked())
):];
template for (constexpr info mem: {mems...})
{ std::println("{}", meta::display_string_of(mem)); }
}

この形式の template for はパック展開 mems... の各要素に対して
std::println(…); を展開する。
��/��

24.

使⽤例 ��/��

25.
[beta]
その1: 列挙型⇔⽂字列
template<typename E> requires std::is_enum_v<E>
constexpr E from_string(std::string_view s) {
constexpr auto etors =
std::define_static_array(meta::enumerators_of(^^E));
template for (constexpr info etor: auto(etors)) {
constexpr E val = meta::extract<E>(etor);
if (s == meta::identifier_of(etor)) return val;
}
throw std::out_of_range("out of range");
}

��/��

26.
[beta]
その1: 列挙型⇔⽂字列 (続き)
template<typename E> requires std::is_enum_v<E>
constexpr std::string_view to_string(E e) {
constexpr auto etors =
std::define_static_array(meta::enumerators_of(^^E));
template for (constexpr info etor: auto(etors)) {
constexpr E val = meta::extract<E>(etor);
if (e == val) return meta::identifier_of(etor);
}
throw std::out_of_range("out of range");
}

��/��

27.
[beta]
その1: 列挙型⇔⽂字列 (続き)
enum struct color { red, green, blue };
int main() {
color e = from_string<color>("green");
assert(e == color::green);
std::println("{}", to_string(e));
}

��/��

28.
[beta]
その2: 侵襲的なカスタム実装
struct hashable_marker {};
constexpr hashable_marker hashable{};
struct hasher_marker {};
constexpr hasher_marker hasher{};
struct [[=hashable]] point {
// …
std::size_t hash [[=hasher]]() const;
};
template<typename T>
requires /* Tはhashable_markerアノテーションを持つ */
std::hash<T> {
constexpr info hash_mem =
/* hasher_markerアノテーションを持つTのメンバーを探す */;
static std::size_t hash(T const & x) const {
return x.[:hash_mem:]();
}
};

��/��

29.
[beta]
その3: キーワード引数
template<typename ...Args>
void f(Args && ...args) {
kwarg_set aset { std::forward<Args>(args)..., arg<"z"> = 56 };
std::println("x={}, y={}, z={}", aset.x, aset.y, aset.z);
}
int main() {
f(arg<"x"> = 12, arg<"y"> = 34);
f(arg<"y"> = 34, arg<"x"> = 12);
f(arg<"z"> = 78, arg<"y"> = 34, arg<"x"> = 12);
}

指⽰付き初期化と違って順不同に指定できる。

��/��

30.
[beta]
その4: コマンドラインパーサー
struct option {
std::string output
[[=short_name('o'), =var("FILE"), =help("output file")]];
bool verbose
[[=short_name('v'), =help("verbose log")]];
};

というクラス定義から、
cmd --output hoge.txt --verbose
cmd -vo hoge.txt
cmd --help
Options:
-o, --output FILE output file
-v, --verbose
verbose log
-h, --help
show help and exit

のような形式のコマンドライン引数を解析できるパーサーを⽣
成する。
��/��