C++26 Reflection進入標準了,用一個實際的例子來證明這東西有什麼用
當我們有這樣一個struct時
1 | struct NetworkAddress { |
如果希望使用std::format
系列的函數搭配使用
需要自行定義formatter
1 | template <> |
直接使用就可以了
1 | NetworkAddress addr { "127.0.0.1", 80 }; |
不過當你自定義的structure多的話,手寫和維護formatter變成一個工程上的問題
因此我們需要一個自動化的方法
他山之石
Rust是個不錯的參考方案,多虧了Proc Macro這種黑魔法,可以寫出類似這樣的程式碼,也是Rust的殺手鐧之一
1 |
|
希望之後C++的版本也能這麼乾淨
用魔法打敗魔法
在C++26 Reflection之前,已經有一個解決方案了,完整程式碼可以參考Reference
借助Boost Hana
之便,可以寫出
1 | namespace hana = boost::hana; |
雖然能用,不過C++26都要出了,需要嘗試更好的方法了
Generate string literal in compile time
先跳過反射的部分,解決比較小的問題
如何在compile time對字串做處理
我想寫一個Compile time function,Pseudo Code大概長這樣
1 | consteval const char* make_greeting(std::string_view name) { |
上面的????
就是難點所在
在C++26之前,看得到的解法大概長這樣
1 | template <size_t N> |
原理跟上面的GenerateFormatStringImpl
差不多
不過這也有它的問題
- 不能直接套用std::string的方式,導致於更複雜的字串處理很難過
- The constexpr 2-Step,上面的greeting_
和greeting
都是必須存在的
Reference中有對上面更進一步的最佳化,不過非本文重點,有興趣自行研究
在C++26 Reflection通過之後,有一個小功能也順便進入標準了,因此我們可以這樣寫了
1 | consteval const char* make_greeting(std::string_view name) { |
這就是我們之後產生struct layout description的基礎
C++26 Reflection Revisited
因為引進了反射,我們可以直接得到struct中每個field的名稱了
1 | struct NetworkAddress { |
現在解決第一部份了,看看剩下的部分
Revisited Hana’s implementation
1 | template <typename T> |
原來是這樣
- 將t的members打包成tuple
- 透過
haha::unpack
展開tuple中的所有元素,將其餵入std::format_to當參數- 既然我們已經有
std::apply
了,就不需要hana::unpack
了,剩下的就是將struct打包成tuple這個問題了struct_to_tuple
在C++ Reflection論文中就有一個現成的實作,直接套來用這時候以下的程式碼就能正常運作了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32consteval auto type_struct_to_tuple(std::meta::info type) -> std::meta::info {
constexpr auto ctx = std::meta::access_context::current();
return substitute(^^std::tuple,
nonstatic_data_members_of(type, ctx)
| std::views::transform(std::meta::type_of)
| std::views::transform(std::meta::remove_cvref)
| std::ranges::to<std::vector>());
}
template <typename To, typename From, std::meta::info ... members>
constexpr auto struct_to_tuple_helper(From const& from) -> To {
return To(from.[:members:]...);
}
template <typename From>
consteval auto get_struct_to_tuple_helper() {
using To = [: type_struct_to_tuple(^^From) :];
auto ctx = std::meta::access_context::current();
std::vector args = {^^To, ^^From};
for (auto mem : nonstatic_data_members_of(^^From, ctx)) {
args.push_back(reflect_constant(mem));
}
return extract<To(*)(From const&)>(
substitute(^^struct_to_tuple_helper, args));
}
template <typename From>
constexpr auto struct_to_tuple(From const& from) {
return get_struct_to_tuple_helper<From>()(from);
}1
2
3
4
5
6
7
8
9template <>
struct std::formatter<NetworkAddress> : std::formatter<std::string_view> {
auto format(const NetworkAddress& t, std::format_context& ctx) const {
auto tuple = struct_to_tuple(t);
return std::apply([&](auto&&... args) {
return std::format_to(ctx.out(), FormatString<NetworkAddress>(), args...);
}, tuple);
}
};Little issue
上面的程式碼雖然可以運作,不過距離一般化差很遠,這樣的程式碼是不行的看看Hana的signature1
2
3
4
5
6
7
8
9template <typename T>
struct std::formatter<T> : std::formatter<std::string_view> {
auto format(const T& t, std::format_context& ctx) const {
auto tuple = struct_to_tuple(t);
return std::apply([&](auto&&... args) {
return std::format_to(ctx.out(), FormatString<T>(), args...);
}, tuple);
}
};依樣畫葫蘆,我們使用variable template和concept就能達成這目標了1
2template <typename T>
struct std::formatter<T, std::enable_if_t<hana::Struct<T>::value, char>>不過要像Rust這樣標記1
2
3
4
5
6
7
8
9
10template <typename T>
constexpr bool can_be_formatter = false;
template <>
constexpr bool can_be_formatter<NetworkAddress> = true;
template <typename T> requires(can_be_formatter<T>)
struct std::formatter<T> : std::formatter<std::string_view> {
// ignore
};我們需要另一個C++26特性1
2
3
4
5
struct NetworkAddress {
ip: String,
port: u16
}Annotations
Annotations
Annotation的定義和API就不說了,直接擷取跟我們需要的功能接著修改我們的1
2
3
4
5
6
7
8
9
10
11
12
13template <auto V> struct Derive { };
template <auto V> inline constexpr Derive<V> derive;
inline constexpr struct{} Debug;
template <typename T>
consteval auto has_annotation(std::meta::info r, T const& value) -> bool {
auto expected = std::meta::reflect_constant(value);
for (std::meta::info a : annotations_of(r))
if (std::meta::constant_of(a) == expected)
return true;
return false;
}std::formatter
接著修改最後的1
2
3
4template <typename T> requires (has_annotation(^^T, derive<Debug>))
struct std::formatter<T> : std::formatter<std::string_view> {
// ignore
};NetworkAddress
到此結束,就可以跟Boost Hana說再見了1
2
3struct [[=derive<Debug>]] NetworkAddress {
// ignore
};
- 既然我們已經有
Reference
# C++ Reflection in under 100 lines of code
# c++ 模板元编程简化format
# C++26 反射元编程:Spec API 注入模型
# 如何保存constexpr string的值在运行期使用?
Reflection for C++26
Annotations for Reflection
Reflection for C++26!!!
Code Generation in Rust vs C++26