[英]C++ iterate over members of struct
说我有一个结构:
struct Boundary {
int top;
int left;
int bottom;
int right;
}
和一个向量
std::vector<Boundary> boundaries;
访问结构以分别获得top
、 left
、 bottom
和right
的总和的最 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.