英文:
How to find a struct member using key-value semantic in c++?
问题
我正在尝试实现一个系统,以便我可以使用描述字段类型的字符串名称遍历结构体树。对我来说,主要的约束如下:
- 不能修改原始结构体,但可以在之后添加宏或处理函数。
- 必须使用C++17或更低版本。
例如,这些结构体是:
namespace example {
struct Object1 {
int32_t member1;
int32_t member2;
std::string member3;
};
struct Object2 {
int32_t member1;
int32_t member2;
int32_t member3;
Object1 member4;
};
} // namespace example
我希望能够像下面这样遍历嵌套的结构体:
void do_some_operation(Object2 const& obj, std::string search_str) {
auto result = obj[search_str];
std::cout << "Result: " << result << '\n';
}
如果search_str = 'member1'
,我应该获得Object2实例的member1的值,如果search_str = 'member4.member3'
,我应该获得Object1实例的member3。
我知道我可以使用Boost Reflection来实现这个,但我没有任何经验。
英文:
I am trying to implement a system such that I can traverse a tree of structs
using string name describing the field type. The main constraints for me are as
follows :-
- Cannot modify the original struct, but can add macros or processing functions after the fact.
- Has to be c++17 or lower.
The structs for e.g. are -
namespace example {
struct Object1 {
int32_t member1;
int32_t member2;
std::string member3;
};
struct Object2 {
int32_t member1;
int32_t member2;
int32_t member3;
Object1 member4;
};
} // namespace example
I want to be able to traverse a nested struct like follows -
void do_some_operation(Object2 const& obj, std::string search_str) {
auto result = obj[search_str];
std::cout << "Result: " << result << '\n';
}
Where if search_str = 'member1'
, I should get the value of member1 of Object2
instance, if search_str = 'member4.member3'
, I should get the member3 of the
Object1 instance.
I know I can use boost reflections to achieve this somehow, but I do not have
any experience with this.
答案1
得分: 2
你必须自己实现它。你可以使用Boost Describe来完成繁重的工作。
注意:我建议使用已经实现了大部分你需要的功能的东西。你可以考虑使用Boost JSON,它支持JSON指针,非常接近你想要的功能。你甚至可以使用Boost Python或Lua绑定(如Sol2)等更全面的解决方案。原则上,避免重复造轮子。
使用Boost Describe
#include <boost/describe.hpp>
#include <iostream>
namespace bd = boost::describe;
namespace example {
struct Object1 {
int32_t member1;
int32_t member2;
std::string member3;
};
struct Object2 {
int32_t member1;
int32_t member2;
int32_t member3;
Object1 member4;
};
BOOST_DESCRIBE_STRUCT(Object1, (), (member1, member2, member3))
BOOST_DESCRIBE_STRUCT(Object2, (), (member1, member2, member3, member4))
bool do_some_operation(int32_t i, std::string_view path) {
bool valid = path.empty();
if (valid) {
std::cout << "Result: " << i << std::endl;
}
return valid;
}
bool do_some_operation(std::string const& s, std::string_view path) {
bool valid = path.empty();
if (valid) {
std::cout << "Result: " << s << std::endl;
}
return valid;
}
template <typename T, typename D = bd::describe_members<T, bd::mod_public>>
bool do_some_operation(T const& obj, std::string_view path) {
bool valid = false;
boost::mp11::mp_for_each<D>([&](auto descriptor) {
std::string_view name(descriptor.name);
if (path.starts_with(name)) {
auto& subobj = obj.*descriptor.pointer;
path.remove_prefix(name.length());
if (path.starts_with("."))
path.remove_prefix(1);
valid |= do_some_operation(subobj, path);
}
});
return valid;
}
} // namespace example
int main() {
example::Object2 o { 111, 222, 333, { -111, -222, "Hello world" } };
assert(do_some_operation(o, "member1"));
assert(do_some_operation(o, "member2"));
assert(do_some_operation(o, "member3"));
assert(do_some_operation(o, "member4.member1"));
assert(do_some_operation(o, "member4.member2"));
assert(do_some_operation(o, "member4.member3"));
assert(!do_some_operation(o, "member4"));
assert(!do_some_operation(o, "member4.member4"));
assert(!do_some_operation(o, "member5.member1"));
}
输出:
Result: 111
Result: 222
Result: 333
Result: -111
Result: -222
Result: Hello world
使用Boost JSON
这首先将数据转换为等效的JSON:
{
"member1": 111,
"member2": 222,
"member3": 333,
"member4": {
"member1": -111,
"member2": -222,
"member3": "Hello world"
}
}
然后使用JSON指针来访问子对象。语法有点不同,但它实现了你可能需要的功能,比如能够返回各种子对象类型和处理/索引数组等。
#include <boost/json/src.hpp>
#include <iostream>
namespace bj = boost::json;
namespace example {
struct Object1 {
int32_t member1;
int32_t member2;
std::string member3;
};
struct Object2 {
int32_t member1;
int32_t member2;
int32_t member3;
Object1 member4;
};
static inline void tag_invoke(bj::value_from_tag, bj::value& v, Object1 const& o) {
v = {
{"member1", o.member1},
{"member2", o.member2},
{"member3", o.member3},
};
}
static inline void tag_invoke(bj::value_from_tag, bj::value& v, Object2 const& o) {
v = {
{"member1", o.member1},
{"member2", o.member2},
{"member3", o.member3},
{"member4", bj::value_from(o.member4)},
};
}
} // namespace example
int main() {
example::Object2 o { 111, 222, 333, { -111, -222, "Hello world" } };
auto json = bj::value_from(o);
std::cout << json.at_pointer("/member1") << "\n";
std::cout << json.at_pointer("/member2") << "\n";
std::cout << json.at_pointer("/member3") << "\n";
std::cout << json.at_pointer("/member4/member1") << "\n";
std::cout << json.at_pointer("/member4/member2") << "\n";
std::cout << json.at_pointer("/member4/member3") << "\n";
}
输出:
111
222
333
-111
-222
"Hello world"
英文:
You have to implement it yourself. You can use Boost Describe to do the heavy lifting.
Note: I would consider using something that already implements most of what you need. You could consider Boost JSON which supports JSON Pointers - pretty close to what you want. You could even go the full length and use e.g. Boost Python or e.g. Lua bindings (like Sol2). In principle, avoid re-inventing the wheel
Using Boost Describe
#include <boost/describe.hpp>
#include <iostream>
namespace bd = boost::describe;
namespace example {
struct Object1 {
int32_t member1;
int32_t member2;
std::string member3;
};
struct Object2 {
int32_t member1;
int32_t member2;
int32_t member3;
Object1 member4;
};
BOOST_DESCRIBE_STRUCT(Object1, (), (member1, member2, member3))
BOOST_DESCRIBE_STRUCT(Object2, (), (member1, member2, member3, member4))
bool do_some_operation(int32_t i, std::string_view path) {
bool valid = path.empty();
if (valid) {
std::cout << "Result: " << i << std::endl;
}
return valid;
}
bool do_some_operation(std::string const& s, std::string_view path) {
bool valid = path.empty();
if (valid) {
std::cout << "Result: " << s << std::endl;
}
return valid;
}
template <typename T, typename D = bd::describe_members<T, bd::mod_public>>
bool do_some_operation(T const& obj, std::string_view path) {
bool valid = false;
boost::mp11::mp_for_each<D>([&](auto descriptor) {
std::string_view name(descriptor.name);
if (path.starts_with(name)) {
auto& subobj = obj.*descriptor.pointer;
path.remove_prefix(name.length());
if (path.starts_with("."))
path.remove_prefix(1);
valid |= do_some_operation(subobj, path);
}
});
return valid;
}
} // namespace example
int main() {
example::Object2 o { 111, 222, 333, { -111, -222, "Hello world" } };
assert(do_some_operation(o, "member1"));
assert(do_some_operation(o, "member2"));
assert(do_some_operation(o, "member3"));
assert(do_some_operation(o, "member4.member1"));
assert(do_some_operation(o, "member4.member2"));
assert(do_some_operation(o, "member4.member3"));
assert(!do_some_operation(o, "member4"));
assert(!do_some_operation(o, "member4.member4"));
assert(!do_some_operation(o, "member5.member1"));
}
Prints
Result: 111
Result: 222
Result: 333
Result: -111
Result: -222
Result: Hello world
Using Boost JSON
This converts the data into the equivalent JSON first:
{
"member1": 111,
"member2": 222,
"member3": 333,
"member4": {
"member1": -111,
"member2": -222,
"member3": "Hello world"
}
}
And then uses JSON pointer to address subobjects. The syntax is a little different, but it implements goodies that you may need - like being able to return variant subobject types and handling/indexing into arrays.
#include <boost/json/src.hpp>
#include <iostream>
namespace bj = boost::json;
namespace example {
struct Object1 {
int32_t member1;
int32_t member2;
std::string member3;
};
struct Object2 {
int32_t member1;
int32_t member2;
int32_t member3;
Object1 member4;
};
static inline void tag_invoke(bj::value_from_tag, bj::value& v, Object1 const& o) {
v = {
{"member1", o.member1},
{"member2", o.member2},
{"member3", o.member3},
};
}
static inline void tag_invoke(bj::value_from_tag, bj::value& v, Object2 const& o) {
v = {
{"member1", o.member1},
{"member2", o.member2},
{"member3", o.member3},
{"member4", bj::value_from(o.member4)},
};
}
} // namespace example
int main() {
example::Object2 o { 111, 222, 333, { -111, -222, "Hello world" } };
auto json = bj::value_from(o);
std::cout << json.at_pointer("/member1") << "\n";
std::cout << json.at_pointer("/member2") << "\n";
std::cout << json.at_pointer("/member3") << "\n";
std::cout << json.at_pointer("/member4/member1") << "\n";
std::cout << json.at_pointer("/member4/member2") << "\n";
std::cout << json.at_pointer("/member4/member3") << "\n";
}
Prints
111
222
333
-111
-222
"Hello world"
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论