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

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

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

问题

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

  1. 不能修改原始结构体,但可以在之后添加宏或处理函数。
  2. 必须使用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 :-

  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 -

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&amp; obj, std::string search_str) {
    auto result = obj[search_str];
    std::cout &lt;&lt; &quot;Result: &quot; &lt;&lt; result &lt;&lt; &#39;\n&#39;;
}

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上查看示例

#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

在Coliru上查看示例

这首先将数据转换为等效的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

Live On Coliru

#include &lt;boost/describe.hpp&gt;
#include &lt;iostream&gt;
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 &lt;&lt; &quot;Result: &quot; &lt;&lt; i &lt;&lt; std::endl;
}
return valid;
}
bool do_some_operation(std::string const&amp; s, std::string_view path) {
bool valid = path.empty();
if (valid) {
std::cout &lt;&lt; &quot;Result: &quot; &lt;&lt; s &lt;&lt; std::endl;
}
return valid;
}
template &lt;typename T, typename D = bd::describe_members&lt;T, bd::mod_public&gt;&gt;
bool do_some_operation(T const&amp; obj, std::string_view path) {
bool valid = false;
boost::mp11::mp_for_each&lt;D&gt;([&amp;](auto descriptor) {
std::string_view name(descriptor.name);
if (path.starts_with(name)) {
auto&amp; subobj = obj.*descriptor.pointer;
path.remove_prefix(name.length());
if (path.starts_with(&quot;.&quot;))
path.remove_prefix(1);
valid |= do_some_operation(subobj, path);
}
});
return valid;
}
} // namespace example
int main() {
example::Object2 o { 111, 222, 333, { -111, -222, &quot;Hello world&quot; } };
assert(do_some_operation(o, &quot;member1&quot;));
assert(do_some_operation(o, &quot;member2&quot;));
assert(do_some_operation(o, &quot;member3&quot;));
assert(do_some_operation(o, &quot;member4.member1&quot;));
assert(do_some_operation(o, &quot;member4.member2&quot;));
assert(do_some_operation(o, &quot;member4.member3&quot;));
assert(!do_some_operation(o, &quot;member4&quot;));
assert(!do_some_operation(o, &quot;member4.member4&quot;));
assert(!do_some_operation(o, &quot;member5.member1&quot;));
}

Prints

Result: 111
Result: 222
Result: 333
Result: -111
Result: -222
Result: Hello world

Using Boost JSON

Live On Coliru

This converts the data into the equivalent JSON first:

{
    &quot;member1&quot;: 111,
    &quot;member2&quot;: 222,
    &quot;member3&quot;: 333,
    &quot;member4&quot;: {
        &quot;member1&quot;: -111,
        &quot;member2&quot;: -222,
        &quot;member3&quot;: &quot;Hello world&quot;
    }
}

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 &lt;boost/json/src.hpp&gt;
#include &lt;iostream&gt;
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&amp; v, Object1 const&amp; o) {
v = {
{&quot;member1&quot;, o.member1},
{&quot;member2&quot;, o.member2},
{&quot;member3&quot;, o.member3},
};
}
static inline void tag_invoke(bj::value_from_tag, bj::value&amp; v, Object2 const&amp; o) {
v = {
{&quot;member1&quot;, o.member1},
{&quot;member2&quot;, o.member2},
{&quot;member3&quot;, o.member3},
{&quot;member4&quot;, bj::value_from(o.member4)},
};
}
} // namespace example
int main() {
example::Object2 o { 111, 222, 333, { -111, -222, &quot;Hello world&quot; } };
auto json = bj::value_from(o);
std::cout &lt;&lt; json.at_pointer(&quot;/member1&quot;) &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; json.at_pointer(&quot;/member2&quot;) &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; json.at_pointer(&quot;/member3&quot;) &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; json.at_pointer(&quot;/member4/member1&quot;) &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; json.at_pointer(&quot;/member4/member2&quot;) &lt;&lt; &quot;\n&quot;;
std::cout &lt;&lt; json.at_pointer(&quot;/member4/member3&quot;) &lt;&lt; &quot;\n&quot;;
}

Prints

111
222
333
-111
-222
&quot;Hello world&quot;

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

发表评论

匿名网友

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

确定