Why Refelection
有些時候,我們需要遍歷struct/class的member,最常見的的用途就是print/serialization/deserialization
1 | struct obj { |
這樣子的做法雖然直接,不過有幾個問題
- 只要structure改變,你的implementation就要跟著改變
- 假設要一直支持新的structure,我們需要一個新的overload function
另外有時候我們也需要 struct field name的資訊,例如我們想知道struct file的名稱,而Compiler編譯出來的程式碼沒有struct/class的field資訊,所以我們會這樣手動寫死
1 | void print(const obj& o) |
如果我們把a名稱改成a1,也是要手動維護程式碼,那有什麼適合的方案嗎
Compilier dependent solution
clang的__builtin_dump_struct
只支援dump功能,其他沒了,也只有clang能用
1 | struct obj1 { |
Wrong Idea
想到最直覺的方法,當然是這樣寫
1 | template <typename T> |
不過眾所周知,for loop不能這樣用
Boost pfr for resuce
山不轉路轉,有無數的聰明人想出了方法,其中最有名的就是boost pfr
1 | #include <boost/pfr/ops.hpp> |
不過這方法也是有其侷限性
- 增加了對
boost pfr
的依賴 - 只能對Aggregate type使用
- 不能解決field name的問題
nameof
一個借鑑於C#的library
大概的用法是這樣子對單一變數效果還行,不過對struct/class裡面的field name還是無能為力1
2NAMEOF(somevar) -> "somevar"
NAMEOF(person.address.zip_code) -> "zip_code"
Macro based Solution
以Boost Hana為例
1 |
|
光看程式碼就猜的到,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 | struct S { |
不過前途未卜啊,搞不好像NetworkTS那樣推倒重來,C++23是無望了