现代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";
}

全部评论

相关推荐

king122:实习经历可以重点写这里这里写的清晰一点,分点写。技能特长一般是放在上面的,而且你的实习经历不能只写实现了一些简单的接口,你要去写一些难点和亮点。甚至可以写一些数字指标上去,只要你能配合业务讲出来,根据我说的这些自己简单包装一下,面试应该会更多,至于笔试和八股,那就只能纯靠自己了,对项目包装感兴趣可以找我
点赞 评论 收藏
分享
评论
2
3
分享

创作者周榜

更多
牛客网
牛客企业服务