veselink1/refl-cpp

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

psaripp opened this issue · 4 comments

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

  • nested types
  • stl containers
  • dynamic types / inheriting
    I was experimenting with this library if it could be used to replace a lot of extra overhead including the serialization. However, I've found no way to apply stl type traits to determine if a member is either a pointer or a class (struct) or something else (using my own type traits).

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?

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.

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

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?

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.