现代C++学习——遍历对象的所有数据成员
我们希望实现这样的一个功能,对于一个对象,打印出其所有的成员变量的值。
struct X{int a;double b;char c;}; for_each(X{114,5.14,'x'}); struct Y{double a;std::string b;}; for_each(Y{3.14,"hello world"});
输出
114 5.14 x
3.14 hello world
注:本文章来源于mq白老师的最新的文章mq白:C++那些无比复杂的模板真的有意义吗? 其中提到了C++17的一种方法,但是没有解析,所以这里我写个文章来解释一下为什么可以。
首先在C++17中,引入了结构化绑定https://zh.cppreference.com/w/cpp/language/structured_binding
我们可以很轻松的访问出数据成员。
void test(){ struct X{int a;double b;char c;}; X x{3,3.14,'x'}; auto &[u,v,w] = x; std::cout<<u<<" "<<v<<" "<<w<<"\n"; }
但是我们需要知道一个对象,具体有多少个成员变量。
这个操作听起来不太现实,因为C++并没有给我们提供这样的功能,所以我们需要用到一些元编程的技巧。
这里我们需要一个知识点SFINAE 严格鸽:现代C++学习——模板SFINAE
我们可以利用这个来进行匹配,考虑什么函数可以有参数的个数。
为了可以匹配上任意的成员变量,可以整一个any类,用来可以转换成任意的类型。
struct Any { template <typename T> operator T(); };
使用
此时,我们就可以利用SFINAE来进行匹配了
template <typename T> auto foo(T x) ->decltype(T{Any{},Any{},Any{},Any{}},0u) { return 4; } template <typename T> auto foo(T x) ->decltype(T{Any{},Any{},Any{}},0u) { return 3; } void test() { struct X{int a,b,c;}; std::cout<<foo(X{})<<"\n"; }
看上去很不错,X匹配到了3个参数的位置上。
但是实际上,这样是有问题的。
如果X具有4个参数的话,就都可以匹配上
所以我们需要额外的信息了。
struct A{}; struct B : A{}; struct C : B{}; void foo(A){std::cout<<"A\n";}; void foo(B){std::cout<<"B\n";}; void test() { foo(C{}); }
理论上,两个函数都可以匹配上,但是foo(B) 会被优先匹配。
添加新的东西
template <size_t N> struct tag : tag<N - 1> {}; template <> struct tag<0> {}; template <typename T> constexpr auto size_(tag<4>) -> decltype(T{Any{}, Any{}, Any{}, Any{}}, 0u) { return 4u; } template <typename T> constexpr auto size_(tag<3>) -> decltype(T{Any{}, Any{}, Any{}}, 0u) { return 3u; } template <typename T> constexpr auto size_(tag<2>) -> decltype(T{Any{}, Any{}}, 0u) { return 2u; } template <typename T> constexpr auto size_(tag<1>) -> decltype(T{Any{}}, 0u) { return 1u; } template <typename T> constexpr auto size_(tag<0>) -> decltype(T{}, 0u) { return 0u; } template <typename T> constexpr size_t size() { return size_<T>(tag<4>{}); }
这个时候,一个具有三个成员变量的类,可以成功实例化
size_(tag<3>) size_(tag<2>)size_(tag<1>)size_(tag<0>)
但是因为我们还有个继承关系
tag<4> : tag<3> : tag<2> ....
所以,此时会优先匹配到参数为tag<3>的函数
void test(){ { struct X{int a;int b;}; std::cout<<size<X>()<<"\n"; } { struct X{char a;int b;double c;}; std::cout<<size<X>()<<"\n"; } { struct X{}; std::cout<<size<X>()<<"\n"; } }
输出
2
3
0
此时,我们就可以使用结构化绑定来获得成员变量了。
template <typename T, typename F> void for_each(const T &v, F f) { if constexpr (size<T>() == 3u) { const auto &[m0, m1, m2] = v; f(m0); f(m1); f(m2); } if constexpr (size<T>() == 2u) { const auto &[m0, m1] = v; f(m0); f(m1); } if constexpr (size<T>() == 1u) { const auto &[m0] = v; f(m0); } } template <typename T> void for_each(const T &x) { for_each(x, [](const auto &rhs) { std::cout << rhs << " "; }); std::cout << "\n"; }
使用
int main() { { struct X{int a;double b;}; std::cout<<size<X>()<<"\n"; } { struct X{int a;std::string b;char c;}; std::cout<<size<X>()<<"\n"; } { struct X{int a;double b;char c;}; for_each(X{114,5.14,'x'}); struct Y{double a;std::string b;}; for_each(Y{3.14,"hello world"}); } }
2
3
114 5.14 x
3.14 hello world
可以看到,我们使用的是类似打表的做法,实际上很多语言都有类似的操作,比如rust的元组。
所以,我静态反射呢?
完整代码
struct Any { template <typename T> operator T(); }; template <size_t N> struct tag : tag<N - 1> {}; template <> struct tag<0> {}; template <typename T> constexpr auto size_(tag<4>) -> decltype(T{Any{}, Any{}, Any{}, Any{}}, 0u) { return 4u; } template <typename T> constexpr auto size_(tag<3>) -> decltype(T{Any{}, Any{}, Any{}}, 0u) { return 3u; } template <typename T> constexpr auto size_(tag<2>) -> decltype(T{Any{}, Any{}}, 0u) { return 2u; } template <typename T> constexpr auto size_(tag<1>) -> decltype(T{Any{}}, 0u) { return 1u; } template <typename T> constexpr auto size_(tag<0>) -> decltype(T{}, 0u) { return 0u; } template <typename T> constexpr size_t size() { return size_<T>(tag<4>{}); } template <typename T, typename F> void for_each(const T &v, F f) { if constexpr (size<T>() == 3u) { const auto &[m0, m1, m2] = v; f(m0); f(m1); f(m2); } if constexpr (size<T>() == 2u) { const auto &[m0, m1] = v; f(m0); f(m1); } if constexpr (size<T>() == 1u) { const auto &[m0] = v; f(m0); } } template <typename T> void for_each(const T &x) { for_each(x, [](const auto &rhs) { std::cout << rhs << " "; }); std::cout << "\n"; }