veselink1 / refl-cpp

Static reflection for C++17 (compile-time enumeration, attributes, proxies, overloads, template functions, metaprogramming).
https://veselink1.github.io/refl-cpp/md__introduction.html
MIT License
1.06k stars 77 forks source link

Is there any way to recusively traverse a type or support std type traits? #29

Closed psaripp closed 4 years ago

psaripp commented 4 years ago

I'm working on a comprehensive serializer which has to support a couple of scenarios, such as

I'm using the following example code below:

struct serializable : refl::attr::usage::field, refl::attr::usage::function
{
};

template <typename T> void serialize(std::ostream & os, T && value)
{
    // iterate over the members of T
    for_each(refl::reflect(value).members, [&](auto member) {
        // is_readable checks if the member is a non-const field
        // or a 0-arg const-qualified function marked with property attribute

        if constexpr (is_readable(member) && refl::descriptor::has_attribute<serializable>(member))
        {

            if constexpr (std::is_class_v<decltype(member(value))>) // This is always false
            {
                os << get_display_name(member) << "= {\n";
                serialize(os, member(value)); // This always warns for resuesion
                os << "}\n";
            }
            else
            {
                os << get_display_name(member) << "=";
                os << member(value) << ";"; // This always fails 
            }
        }
    });
}

struct Point
{
    float x;
    float y;
    [[nodiscard]] float magnitude() const { return std::sqrt(x * x + y * y); }
};

REFL_TYPE(Point, bases<>)
REFL_FIELD(x, serializable())
REFL_FIELD(y, serializable())
REFL_FUNC(magnitude)
REFL_END

struct Line
{
    Point start;
    Point end;
    [[nodiscard]] float length() const { return Point({ end.x - start.x, end.y - start.y }).magnitude(); }
};

REFL_TYPE(Line, bases<>)
REFL_FIELD(start, serializable())
REFL_FIELD(end, serializable())
REFL_FUNC(length)
REFL_END

TEST(Reflection, HelloTest)
{
    std::cout << "Custom serialization: ";
    serialize(std::cout, Point{ 1, 1 });

    serialize(std::cout, Line{ { 1, 1 }, { 2, 2 } });
    std::cout << std::endl;

    SUCCEED();
}

I'd like to use it in more complex scenarios like this. Is there any legitimate way to do this?

veselink1 commented 4 years ago

Hello, member(value) returns an lvalue reference to the member object. Try with std::is_class_v<std::remove_reference_t<decltype(member(value))>>.

You might also want to take a look at the implementation of runtime::debug (here), which also supports reflectable types, primitives and arbitrary containers.

veselink1 commented 4 years ago

Also, note that while your comment about is_readable is correct, neither length nor magnitude is marked with property().

veselink1 commented 4 years ago

Inheritance is supported well since v0.11.0, there is no reason why any type traits would fail to work. Member types are exposed via result_type for functions and value_type for fields, and can be obtained by decltype(member(value)) as you've discovered. Stl containers can also be supported easily and are by the built-in write function. Nested types, template types and function and overloaded functions are all supported.

Please, also check the docs for:

Does this answer your question?

psaripp commented 4 years ago

Hi, thanks for the answer, I highly appreciate.

With serialisation, I'm only interested in fields which are both readable and writable (due bi-direction) so marking as property() is not really suitable in this scenario.