如何在C++中使用键值语义查找结构成员?

huangapple go评论117阅读模式
英文:

How to find a struct member using key-value semantic in c++?

问题

我正在尝试实现一个系统,以便我可以使用描述字段类型的字符串名称遍历结构体树。对我来说,主要的约束如下:

  1. 不能修改原始结构体,但可以在之后添加宏或处理函数。
  2. 必须使用C++17或更低版本。

例如,这些结构体是:

  1. namespace example {
  2. struct Object1 {
  3. int32_t member1;
  4. int32_t member2;
  5. std::string member3;
  6. };
  7. struct Object2 {
  8. int32_t member1;
  9. int32_t member2;
  10. int32_t member3;
  11. Object1 member4;
  12. };
  13. } // namespace example

我希望能够像下面这样遍历嵌套的结构体:

  1. void do_some_operation(Object2 const& obj, std::string search_str) {
  2. auto result = obj[search_str];
  3. std::cout << "Result: " << result << '\n';
  4. }

如果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 :-

  1. Cannot modify the original struct, but can add macros or processing functions after the fact.
  2. Has to be c++17 or lower.

The structs for e.g. are -

  1. namespace example {
  2. struct Object1 {
  3. int32_t member1;
  4. int32_t member2;
  5. std::string member3;
  6. };
  7. struct Object2 {
  8. int32_t member1;
  9. int32_t member2;
  10. int32_t member3;
  11. Object1 member4;
  12. };
  13. } // namespace example

I want to be able to traverse a nested struct like follows -

  1. void do_some_operation(Object2 const&amp; obj, std::string search_str) {
  2. auto result = obj[search_str];
  3. std::cout &lt;&lt; &quot;Result: &quot; &lt;&lt; result &lt;&lt; &#39;\n&#39;;
  4. }

Where if search_str = &#39;member1&#39;, I should get the value of member1 of Object2
instance, if search_str = &#39;member4.member3&#39;, 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

在Coliru上查看示例

  1. #include <boost/describe.hpp>
  2. #include <iostream>
  3. namespace bd = boost::describe;
  4. namespace example {
  5. struct Object1 {
  6. int32_t member1;
  7. int32_t member2;
  8. std::string member3;
  9. };
  10. struct Object2 {
  11. int32_t member1;
  12. int32_t member2;
  13. int32_t member3;
  14. Object1 member4;
  15. };
  16. BOOST_DESCRIBE_STRUCT(Object1, (), (member1, member2, member3))
  17. BOOST_DESCRIBE_STRUCT(Object2, (), (member1, member2, member3, member4))
  18. bool do_some_operation(int32_t i, std::string_view path) {
  19. bool valid = path.empty();
  20. if (valid) {
  21. std::cout << "Result: " << i << std::endl;
  22. }
  23. return valid;
  24. }
  25. bool do_some_operation(std::string const& s, std::string_view path) {
  26. bool valid = path.empty();
  27. if (valid) {
  28. std::cout << "Result: " << s << std::endl;
  29. }
  30. return valid;
  31. }
  32. template <typename T, typename D = bd::describe_members<T, bd::mod_public>>
  33. bool do_some_operation(T const& obj, std::string_view path) {
  34. bool valid = false;
  35. boost::mp11::mp_for_each<D>([&](auto descriptor) {
  36. std::string_view name(descriptor.name);
  37. if (path.starts_with(name)) {
  38. auto& subobj = obj.*descriptor.pointer;
  39. path.remove_prefix(name.length());
  40. if (path.starts_with("."))
  41. path.remove_prefix(1);
  42. valid |= do_some_operation(subobj, path);
  43. }
  44. });
  45. return valid;
  46. }
  47. } // namespace example
  48. int main() {
  49. example::Object2 o { 111, 222, 333, { -111, -222, "Hello world" } };
  50. assert(do_some_operation(o, "member1"));
  51. assert(do_some_operation(o, "member2"));
  52. assert(do_some_operation(o, "member3"));
  53. assert(do_some_operation(o, "member4.member1"));
  54. assert(do_some_operation(o, "member4.member2"));
  55. assert(do_some_operation(o, "member4.member3"));
  56. assert(!do_some_operation(o, "member4"));
  57. assert(!do_some_operation(o, "member4.member4"));
  58. assert(!do_some_operation(o, "member5.member1"));
  59. }

输出:

  1. Result: 111
  2. Result: 222
  3. Result: 333
  4. Result: -111
  5. Result: -222
  6. Result: Hello world

使用Boost JSON

在Coliru上查看示例

这首先将数据转换为等效的JSON:

  1. {
  2. "member1": 111,
  3. "member2": 222,
  4. "member3": 333,
  5. "member4": {
  6. "member1": -111,
  7. "member2": -222,
  8. "member3": "Hello world"
  9. }
  10. }

然后使用JSON指针来访问子对象。语法有点不同,但它实现了你可能需要的功能,比如能够返回各种子对象类型和处理/索引数组等。

  1. #include <boost/json/src.hpp>
  2. #include <iostream>
  3. namespace bj = boost::json;
  4. namespace example {
  5. struct Object1 {
  6. int32_t member1;
  7. int32_t member2;
  8. std::string member3;
  9. };
  10. struct Object2 {
  11. int32_t member1;
  12. int32_t member2;
  13. int32_t member3;
  14. Object1 member4;
  15. };
  16. static inline void tag_invoke(bj::value_from_tag, bj::value& v, Object1 const& o) {
  17. v = {
  18. {"member1", o.member1},
  19. {"member2", o.member2},
  20. {"member3", o.member3},
  21. };
  22. }
  23. static inline void tag_invoke(bj::value_from_tag, bj::value& v, Object2 const& o) {
  24. v = {
  25. {"member1", o.member1},
  26. {"member2", o.member2},
  27. {"member3", o.member3},
  28. {"member4", bj::value_from(o.member4)},
  29. };
  30. }
  31. } // namespace example
  32. int main() {
  33. example::Object2 o { 111, 222, 333, { -111, -222, "Hello world" } };
  34. auto json = bj::value_from(o);
  35. std::cout << json.at_pointer("/member1") << "\n";
  36. std::cout << json.at_pointer("/member2") << "\n";
  37. std::cout << json.at_pointer("/member3") << "\n";
  38. std::cout << json.at_pointer("/member4/member1") << "\n";
  39. std::cout << json.at_pointer("/member4/member2") << "\n";
  40. std::cout << json.at_pointer("/member4/member3") << "\n";
  41. }

输出:

  1. 111
  2. 222
  3. 333
  4. -111
  5. -222
  6. "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

Live On Coliru

  1. #include &lt;boost/describe.hpp&gt;
  2. #include &lt;iostream&gt;
  3. namespace bd = boost::describe;
  4. namespace example {
  5. struct Object1 {
  6. int32_t member1;
  7. int32_t member2;
  8. std::string member3;
  9. };
  10. struct Object2 {
  11. int32_t member1;
  12. int32_t member2;
  13. int32_t member3;
  14. Object1 member4;
  15. };
  16. BOOST_DESCRIBE_STRUCT(Object1, (), (member1, member2, member3))
  17. BOOST_DESCRIBE_STRUCT(Object2, (), (member1, member2, member3, member4))
  18. bool do_some_operation(int32_t i, std::string_view path) {
  19. bool valid = path.empty();
  20. if (valid) {
  21. std::cout &lt;&lt; &quot;Result: &quot; &lt;&lt; i &lt;&lt; std::endl;
  22. }
  23. return valid;
  24. }
  25. bool do_some_operation(std::string const&amp; s, std::string_view path) {
  26. bool valid = path.empty();
  27. if (valid) {
  28. std::cout &lt;&lt; &quot;Result: &quot; &lt;&lt; s &lt;&lt; std::endl;
  29. }
  30. return valid;
  31. }
  32. template &lt;typename T, typename D = bd::describe_members&lt;T, bd::mod_public&gt;&gt;
  33. bool do_some_operation(T const&amp; obj, std::string_view path) {
  34. bool valid = false;
  35. boost::mp11::mp_for_each&lt;D&gt;([&amp;](auto descriptor) {
  36. std::string_view name(descriptor.name);
  37. if (path.starts_with(name)) {
  38. auto&amp; subobj = obj.*descriptor.pointer;
  39. path.remove_prefix(name.length());
  40. if (path.starts_with(&quot;.&quot;))
  41. path.remove_prefix(1);
  42. valid |= do_some_operation(subobj, path);
  43. }
  44. });
  45. return valid;
  46. }
  47. } // namespace example
  48. int main() {
  49. example::Object2 o { 111, 222, 333, { -111, -222, &quot;Hello world&quot; } };
  50. assert(do_some_operation(o, &quot;member1&quot;));
  51. assert(do_some_operation(o, &quot;member2&quot;));
  52. assert(do_some_operation(o, &quot;member3&quot;));
  53. assert(do_some_operation(o, &quot;member4.member1&quot;));
  54. assert(do_some_operation(o, &quot;member4.member2&quot;));
  55. assert(do_some_operation(o, &quot;member4.member3&quot;));
  56. assert(!do_some_operation(o, &quot;member4&quot;));
  57. assert(!do_some_operation(o, &quot;member4.member4&quot;));
  58. assert(!do_some_operation(o, &quot;member5.member1&quot;));
  59. }

Prints

  1. Result: 111
  2. Result: 222
  3. Result: 333
  4. Result: -111
  5. Result: -222
  6. Result: Hello world

Using Boost JSON

Live On Coliru

This converts the data into the equivalent JSON first:

  1. {
  2. &quot;member1&quot;: 111,
  3. &quot;member2&quot;: 222,
  4. &quot;member3&quot;: 333,
  5. &quot;member4&quot;: {
  6. &quot;member1&quot;: -111,
  7. &quot;member2&quot;: -222,
  8. &quot;member3&quot;: &quot;Hello world&quot;
  9. }
  10. }

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.

  1. #include &lt;boost/json/src.hpp&gt;
  2. #include &lt;iostream&gt;
  3. namespace bj = boost::json;
  4. namespace example {
  5. struct Object1 {
  6. int32_t member1;
  7. int32_t member2;
  8. std::string member3;
  9. };
  10. struct Object2 {
  11. int32_t member1;
  12. int32_t member2;
  13. int32_t member3;
  14. Object1 member4;
  15. };
  16. static inline void tag_invoke(bj::value_from_tag, bj::value&amp; v, Object1 const&amp; o) {
  17. v = {
  18. {&quot;member1&quot;, o.member1},
  19. {&quot;member2&quot;, o.member2},
  20. {&quot;member3&quot;, o.member3},
  21. };
  22. }
  23. static inline void tag_invoke(bj::value_from_tag, bj::value&amp; v, Object2 const&amp; o) {
  24. v = {
  25. {&quot;member1&quot;, o.member1},
  26. {&quot;member2&quot;, o.member2},
  27. {&quot;member3&quot;, o.member3},
  28. {&quot;member4&quot;, bj::value_from(o.member4)},
  29. };
  30. }
  31. } // namespace example
  32. int main() {
  33. example::Object2 o { 111, 222, 333, { -111, -222, &quot;Hello world&quot; } };
  34. auto json = bj::value_from(o);
  35. std::cout &lt;&lt; json.at_pointer(&quot;/member1&quot;) &lt;&lt; &quot;\n&quot;;
  36. std::cout &lt;&lt; json.at_pointer(&quot;/member2&quot;) &lt;&lt; &quot;\n&quot;;
  37. std::cout &lt;&lt; json.at_pointer(&quot;/member3&quot;) &lt;&lt; &quot;\n&quot;;
  38. std::cout &lt;&lt; json.at_pointer(&quot;/member4/member1&quot;) &lt;&lt; &quot;\n&quot;;
  39. std::cout &lt;&lt; json.at_pointer(&quot;/member4/member2&quot;) &lt;&lt; &quot;\n&quot;;
  40. std::cout &lt;&lt; json.at_pointer(&quot;/member4/member3&quot;) &lt;&lt; &quot;\n&quot;;
  41. }

Prints

  1. 111
  2. 222
  3. 333
  4. -111
  5. -222
  6. &quot;Hello world&quot;

huangapple
  • 本文由 发表于 2023年7月17日 18:08:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76703413.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定