Why Refelection 有些時候,我們需要遍歷struct/class的member,最常見的的用途就是print/serialization/deserialization
1 2 3 4 5 6 7 8 struct obj { int a; }; void print (const obj& o) { printf ("%d\n" , o.a); }
這樣子的做法雖然直接,不過有幾個問題
只要structure改變,你的implementation就要跟著改變
假設要一直支持新的structure,我們需要一個新的overload function
另外有時候我們也需要 struct field name的資訊,例如我們想知道struct file的名稱,而Compiler編譯出來的程式碼沒有struct/class的field資訊,所以我們會這樣手動寫死
1 2 3 4 void print (const obj& o) { printf ("a: %d\n" , o.a); }
如果我們把a名稱改成a1,也是要手動維護程式碼,那有什麼適合的方案嗎
Compilier dependent solution clang的__builtin_dump_struct 只支援dump功能,其他沒了,也只有clang能用
1 2 3 4 5 6 7 8 9 10 struct obj1 { int a; int b; }; int main () { struct obj1 o = { .a=1 , .b=2 }; __builtin_dump_struct(&o, &printf ); return 0 ; }
Wrong Idea 想到最直覺的方法,當然是這樣寫
1 2 3 4 5 6 template <typename T>void print (const T& o) { for (auto & field : { field of o }) std ::cout << field << "\n" ; }
不過眾所周知,for loop不能這樣用
Boost pfr for resuce 山不轉路轉,有無數的聰明人想出了方法,其中最有名的就是boost pfr
1 2 3 4 5 6 7 8 #include <boost/pfr/ops.hpp> template <typename T> void print(const T& o) { boost::pfr::for_each_field(o, [&](const auto& v) { std::cout << v << "\n"; }); }
不過這方法也是有其侷限性
增加了對 boost pfr的依賴
只能對Aggregate type使用
不能解決field name的問題nameof 一個借鑑於C#的library 大概的用法是這樣子1 2 NAMEOF(somevar) -> "somevar" NAMEOF(person.address.zip_code) -> "zip_code"
對單一變數效果還行,不過對struct/class裡面的field name還是無能為力
Macro based Solution 以Boost Hana為例
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 #include <boost/hana.hpp> struct OrderedItem { BOOST_HANA_DEFINE_STRUCT( OrderedItem, (std ::string , item_name), (int64_t , quantity), (int64_t , price_cents) ); }; template <typename T>boost::json::value FormatStructure (const T &t) { boost::json::object result; boost::hana::for_each(t, boost::hana::fuse([&result](auto name, auto member) { result.emplace(boost::hana::to<const char *>(name), FormatObject(member)); })); return result; } template <typename T>boost::json::value FormatObject (const T &t) { if constexpr (boost::hana::Struct<T>::value) { return internal::FormatStructure(t); } else { return internal::FormatValue(t); } }
光看程式碼就猜的到,BOOST_HANA_DEFINE_STRUCT做了很多事情,維護每個除了原先的 field declaration之外,還維護了field name的資訊 不過Macro就是黑魔法,維護起來就是麻煩,不過現階段也沒更好的方法
Runtime Refelection 上面說的都是Compile-time Refelection,當然還有一派作法是在Runtime時做Refelection,能無視編譯器的差異,提供比編譯器更多的Metadata,不過這一切都是要手動做
不管Compile-time Refelectionc還是Runtime Refelection,都掙脫不了Macro和Template的禁錮
Future 有個實驗性質的reflection TS
1 2 3 4 5 6 7 8 9 10 11 struct S { int b; std ::string s; std ::vector <std ::string > v; }; #include <experimental/reflect> using meta_S = reflexpr(S);using mem = std ::reflect::get_data_members_t <meta_S>;using meta = std ::reflect::get_data_members_t <mem>;
不過前途未卜啊,搞不好像NetworkTS那樣推倒重來,C++23是無望了
Reference