繁体   English   中英

C++ 遍历结构体的成员

[英]C++ iterate over members of struct

说我有一个结构:

struct Boundary {
  int top;
  int left;
  int bottom;
  int right;
}

和一个向量

std::vector<Boundary> boundaries;

访问结构以分别获得topleftbottomright的总和的最 C++ 风格的方式是什么?

我可以写一个循环

for (auto boundary: boundaries) {
  sum_top+=boundary.top;
  sum_bottom+=boundary.bottom;
  ...
}

这似乎有很多重复。 当然,我可以这样做:

std::vector<std::vector<int>> boundaries;

for (auto boundary: boundaries) {
  for(size_t i=0; i<boundary.size();i++) {
    sums.at(i)+=boundary.at(i)
  }
}

但随后我会丢失所有有意义的结构成员名称。 有没有办法让我可以编写类似以下函数的内容:

sum_top=make_sum(boundaries,"top");

反射似乎不是 C++ 中的一个选项。 我愿意使用 C++ 到版本 14。

std::accumulate(boundaries.begin(), boundaries.end(), 0, 
 [](Boundary const & a, Boundary const & b) { return a.top + b.top); });

(IIRC 边界常量 & 可以在 C++17 中auto “d”)

这并不使它对于特定元素具有通用性,实际上,由于缺乏反射,它不容易概括。

不过,有几种方法可以减轻您的痛苦;

您可以使用指向成员的指针,这对您的 szenario 很好,但不是很 c-plusplus-y:

int Sum(vector<Boundary>const & v, int Boundary::*pMember)
{
   return std::accumulate( /*...*/, 
     [&](Boundary const & a, Boundary const & b)
     {
        return a.*pMember + b.*pMember;
     });
}

int topSum = Sum(boundaries, &Boundary::top);

(对于指向成员的指针,请参见例如此处: 指向类数据成员 "::*" 的指针

你也可以使这个通用(任何容器,任何成员类型),你也可以用 lambda 替换指向成员的指针(也允许成员函数)

您可以使用 Boost Hana 反射实现所需的效果:

#include <iostream>
#include <vector>
#include <boost/hana.hpp>

struct Boundary {
  BOOST_HANA_DEFINE_STRUCT(Boundary,
      (int, top),
      (int, left),
      (int, bottom),
      (int, right)
  );
};

template<class C, class Name>
int make_sum(C const& c, Name name) {
    int sum = 0;
    for(auto const& elem : c) {
        auto& member = boost::hana::at_key(elem, name);
        sum += member;
    }
    return sum;
}

int main() {
    std::vector<Boundary> v{{0,0,1,1}, {1,1,2,2}};

    std::cout << make_sum(v, BOOST_HANA_STRING("top")) << '\n';
    std::cout << make_sum(v, BOOST_HANA_STRING("bottom")) << '\n';
}

有关更多详细信息,请参阅自省用户定义的类型

我参加聚会可能有点晚了,但我想添加受@TobiasRibizel 启发的答案。 我们没有向struct中添加大量样板代码,而是以迭代器的形式添加更多样板代码,以遍历(指定的)结构成员。

#include <iostream>
#include <string>
#include <map>

template<class C, typename T, T C::* ...members>
class struct_it {
public:
    using difference_type = std::ptrdiff_t;
    using value_type = T;
    using pointer = T*;
    using reference = T&;
    using iterator_category = std::bidirectional_iterator_tag;

    constexpr struct_it (C &c) : _index{0}, _c(c) 
    {}

    constexpr struct_it (size_t index, C &c) : _index{index}, _c(c) 
    {}

    constexpr static struct_it make_end(C &c) {
        return struct_it(sizeof...(members), c);
    }

    constexpr bool operator==(const struct_it& other) const {
        return other._index == _index;   // Does not check for other._c == _c, since that is not always possible. Maybe do &other._c == &_c?
    }

    constexpr bool operator!=(const struct_it& other) const {
        return !(other == *this);
    }

    constexpr T& operator*() const {
        return _c.*_members[_index];
    }

    constexpr T* operator->() const {
        return &(_c.*_members[_index]);
    }

    constexpr struct_it& operator--() {
        --_index;
        return *this;
    }

    constexpr struct_it& operator--(int) {
        auto copy = *this;
        --_index;
        return copy;
    }
    constexpr struct_it& operator++() {
        ++_index;
        return *this;
    }

    constexpr struct_it& operator++(int) {
        auto copy = *this;
        ++_index;
        return copy;
    }


private:
    size_t _index;
    C &_c;
    std::array<T C::*, sizeof...(members)> _members = {members...};  // Make constexpr static on C++17
};

template<class C, typename T, T C::* ...members>
using cstruct_it = struct_it<const C, T, members...>;

struct boundary {
    int top;
    int bottom;
    int left;
    int right;

    using iter = struct_it<boundary, int, &boundary::top, &boundary::bottom, &boundary::left, &boundary::right>;
    using citer = cstruct_it<boundary, int, &boundary::top, &boundary::bottom, &boundary::left, &boundary::right>;

    iter begin() {
        return iter{*this};
    }

    iter end() {
        return iter::make_end(*this);
    }

    citer cbegin() const {
        return citer{*this};
    }

    citer cend() const {
        return citer::make_end(*this);
    }
};


int main() {
    boundary b{1,2,3,4};

    for(auto i: b) {
        std::cout << i << ' '; // Prints 1 2 3 4
    }

    std::cout << '\n';
}

它适用于 C++14,在 C++11 上, constexpr函数默认都是const ,所以它们不起作用,但只要去掉constexpr就可以了。 好处是你可以只选择struct一些成员并迭代它们。 如果您有相同的几个成员,您将始终对其进行迭代,您只需添加一个using 这就是为什么我选择将指向成员的指针作为模板的一部分,即使实际上没有必要,因为我认为只有相同成员上的迭代器应该是相同类型的。

也可以保持std::array ,用std::vector替换std::array并在运行时选择要迭代的成员。

在不深入 C++ 对象的内存布局的情况下,我建议用“reference-getters”替换成员,这会向结构中添加一些样板代码,但除了用top()替换top之外,不需要任何更改您使用结构成员的方式。

struct Boundary {
   std::array<int, 4> coordinates;
   int& top() { return coordinates[0]; }
   const int& top() const { return coordinates[0]; }
   // ...
 }

 Boundary sum{};
 for (auto b : boundaries) {
   for (auto i = 0; i < 4; ++i) {
      sum.coordinates[i] += b.coordinates[i];
   }
 }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM