與其說這是Reflection,這個比較像是General meta programming solution 也就是Boost Hana所推薦的類型運算,將Type當作Value,然後對Type做處理的動作 基本的操作就是這樣
1 2 constexpr std ::meta::info value = ^^int ;using Int = typename [:value:];
這裡的info是個opaque type,只有Compiler看得懂,任何的操作要透過Meta function來進行
在開始之前,先寫一個Helper function,好幫助做驗證
Helper function 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 32 33 34 35 36 namespace __impl { template <auto ... vals> struct replicator_type { template <typename F> constexpr void operator >>(F body) const { (body.template operator ()<vals>(), ...); } }; template <auto ... vals> replicator_type<vals...> replicator = {}; } template <typename R>consteval auto expand (R range) { std ::vector <std ::meta::info> args; for (auto r : range) { args.push_back(std ::meta::reflect_value(r)); } return substitute(^^__impl::replicator, args); } template <typename S>constexpr auto print_layout() -> std::string { std ::string desc = "Print struct layout: " + std ::string (identifier_of(^^S)) + "\n" ; desc += "members: {\n" ; [: expand(nonstatic_data_members_of(^^S)) :] >> [&]<auto e>() mutable { desc += " " + std ::string (identifier_of(e)); desc += " " + std ::string (identifier_of(type_of(e))); desc += "\n" ; }; desc += "}" ; return desc; }
測試範例
1 2 3 4 5 6 7 8 9 10 11 12 13 struct X { char a; int b; double c; }; int main () { auto desc = print_layout<X>(); std ::cout << desc << "\n" ; return 0 ; }
無中生有,define_class
最簡單的例子
1 2 3 4 5 6 7 8 9 struct storage ;static_assert (is_type(define_class(^^storage, {data_member_spec(^^int , {.name="value" })})));int main () { auto desc = print_layout<storage>(); std ::cout << desc << "\n" ; return 0 ; }
印出來的結果
1 2 3 4 Print struct layout: storage members: { value int }
從上面的程式碼,可以看出
storage 是 forward declaration,沒有任何定義
真正的定義是在define_class(....)
這裡
必須使用static_assert(is_type(....))
在編譯期完成
宣告member field必須使用data_member_spec
Template class 如果我們想要產生類似1 2 3 4 template <size_t N>struct storage { int value[N]; };
該怎麼做Step1 雖然無關緊要,不過我想把定義class的部分獨立成一個函數1 2 3 4 5 template <size_t N>consteval auto define_storage() -> std::meta::info { return define_class(^^storage, {data_member_spec(^^int , {.name="value" })}); } using T = [:define_storage<10 >():];
這樣省下了static_assert(is_type(...))
的需求Step2 將storage的forward declaration改成這樣1 2 template <size_t N>struct storage ;
Step3 修改define_storage的實作1 2 3 4 5 6 template <size_t N>consteval auto define_storage() -> std::meta::info { return define_class(substitute(^^storage, {^^N}), { data_member_spec(^^int , {.name="value" }) }); }
這邊有個substitute
函數,第一個參數就是tempalte class,之後的參數就是要填入的直了,印出的結果類似這樣1 2 3 4 Print struct layout: storage members: { value int }
距離我們要的還差一點Step4 故技重施,定義一個新type1 2 template <typename T, size_t N>using c_array_t = T[N];
然後再度修改define_storage
1 2 3 4 5 6 template <size_t N>consteval auto define_storage() -> std::meta::info { return define_class(substitute(^^storage, {^^N}), { data_member_spec(substitute(^^c_array_t , {^^int , ^^N}), {.name="value" }) }); }
這下子看到的是1 2 3 4 Print struct layout: storage members: { value c_array_t }
有沒有辦法將c_array_t
變回int[10]
Step5 加上dealias
就行了,不過應該有更好的方法,暫時還沒想到1 2 3 4 5 6 template <size_t N>consteval auto define_storage() -> std::meta::info { return define_class(substitute(^^storage, {^^N}), { data_member_spec(dealias(substitute(^^c_array_t , {^^int , ^^N})), {.name="value" }) }); }
不過這方法跟原先的方法比,最大的好處是可程式化 ,可以創造出更特別的玩法,例如
Full(Partial) Template Specialization 將原先的範例改成這樣
1 2 3 4 5 6 7 8 9 template <size_t N>struct storage { int value[N]; }; template <>struct storage <5> { char value[5 ]; int value2; };
我們一樣可以用define_storage
來做
1 2 3 4 5 6 7 8 9 10 11 template <size_t N>consteval auto define_storage() -> std::meta::info { std ::vector <std ::meta::info> members; if constexpr (N == 5 ) { members.push_back(data_member_spec(dealias(substitute(^^c_array_t , {^^char , ^^N})), {.name="value" })); members.push_back(data_member_spec(^^int , {.name="value2" })); } else { members.push_back(data_member_spec(dealias(substitute(^^c_array_t , {^^int , ^^N})), {.name="value" })); } return define_class(substitute(^^storage, {^^N}), members); }
而Partial Template Specialization 可以用同樣的方式處理
1 2 3 4 5 6 7 8 9 10 11 template <size_t N1, size_t N2>struct storage { int value[N1]; int value2[N2]; }; template <size_t N1>struct storage <N1, 5> { int value1[N1]; char value2[5 ]; };
之後可以這樣寫
1 2 3 4 5 6 7 8 9 10 11 12 13 template <size_t N1, size_t N2>struct storage ;template <size_t N1, size_t N2>consteval auto define_storage() -> std::meta::info { std ::vector <std ::meta::info> members; members.push_back(data_member_spec(dealias(substitute(^^c_array_t , {^^int , ^^N1})), {.name="value1" })); if constexpr (N2 == 5 ) { members.push_back(data_member_spec(dealias(substitute(^^c_array_t , {^^char , ^^N2})), {.name="value2" })); } else { members.push_back(data_member_spec(dealias(substitute(^^c_array_t , {^^int , ^^N2})), {.name="value2" })); } return define_class(substitute(^^storage, {^^N1, ^^N2}), members); }
Implement Pick/Omit in TypeScript Typescript裡面有一個Pick Utility
1 2 3 4 5 6 type Person = { firstName: string ; lastName: string ; age: number ; }; type PersonName = Pick<Person, 'firstName' | 'lastName' >;
這裡的PersonName
就等同於
1 2 3 4 type PersonName = { firstName: string ; lastName: string ; };
C++的等價版本應該是這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 template <typename From, typename To>consteval auto Pick(std::initializer_list<std::string_view> keeps) -> std::meta::info { std ::vector <std ::meta::info> members; [: expand(nonstatic_data_members_of(^^From)) :] >> [&]<auto e>() mutable { auto it = std ::ranges::find(keeps, identifier_of(e)); if (it != keeps.end()) { members.push_back(data_member_spec(type_of(e), {.name=identifier_of(e)})); } }; return define_class(^^To, members); } struct Person { std ::string firstName; std ::string lastName; int age; }; struct PersonName ;static_assert (is_type(Pick<Person, PersonName>({ "firstName" , "lastName" })));
應該可以更好,不過目前想不到怎麼做 Omit只是Pick的反操作,這邊就不寫了
Revisit Boost MP11 以下是MP11的一個範例
1 2 3 using L = std ::tuple<void , int , float >;using R = mp_transform<std ::add_pointer_t , L>;static_assert (std ::is_same_v<R, std ::tuple<void *, int *, float *>>);
可以用Meta Programming重寫
1 2 3 4 5 6 7 8 9 10 11 template < typename T>consteval auto transform(std::meta::info (&F)(std::meta::info)) -> std::meta::info { std ::vector <std ::meta::info> new_members; for (auto member : template_arguments_of(^^T)) { new_members.push_back(F(member)); } return substitute(template_of(^^T), new_members); } using L = std ::tuple<void , int , float >;using R = [:transform<L>(std ::meta::type_add_pointer):];static_assert (std ::is_same_v<R, std ::tuple<void *, int *, float *>>);
省去了很多以往的Template Magic
Conclusion Reflection如果如期進入C++26,會是極大的變化 只希望一切順利
Reference