MRDOCS_DESCRIBE_STRUCT
Describe the bases and members of a class, outside its definition.
Description
Place it at namespace scope, in the namespace of the class, after the class definition. Bases and Members are parenthesized, comma‐separated lists, and either one may be empty:
namespace geo {
struct Shape
{
std::string name;
};
struct Point : Shape
{
int x = 0;
int y = 0;
};
struct Tag {};
MRDOCS_DESCRIBE_STRUCT(Shape, (), (name))
MRDOCS_DESCRIBE_STRUCT(Point, (Shape), (x, y))
MRDOCS_DESCRIBE_STRUCT(Tag, (), ())
} // namespace geo
Several bases are listed like several members, e.g. MRDOCS_DESCRIBE_STRUCT(AB, (A, B), (c)). The three lines expand to roughly the following (simplified: the ::mrdocs::describe::detail:: and ::mrdocs::describe:: qualifications are dropped and the assertion message is shortened):
// MRDOCS_DESCRIBE_STRUCT(Shape, (), (name))
static_assert(std::is_class_v<Shape> || std::is_union_v<Shape>, "...");
[[maybe_unused]]
typename bases_descriptor_impl<Shape, list<>>::type
mrdocs_base_descriptor_fn(Shape**);
[[maybe_unused]]
decltype(member_descriptor_fn_impl(
0,
member_descriptor<&Shape::name, []{ return "name"; }>{}))
mrdocs_member_descriptor_fn(Shape**);
// MRDOCS_DESCRIBE_STRUCT(Point, (Shape), (x, y))
// Rejects anything that isn't a class or a union.
static_assert(std::is_class_v<Point> || std::is_union_v<Point>, "...");
// Declared, never defined or called: only the return type matters.
// ADL finds it from a `Point**`, and the return type lists the bases.
[[maybe_unused]]
typename bases_descriptor_impl<Point, list<Shape>>::type
mrdocs_base_descriptor_fn(Point**);
// Same trick for the members: one descriptor per name, holding the
// member pointer and the name as a string.
[[maybe_unused]]
decltype(member_descriptor_fn_impl(
0,
member_descriptor<&Point::x, []{ return "x"; }>{},
member_descriptor<&Point::y, []{ return "y"; }>{}))
mrdocs_member_descriptor_fn(Point**);
// MRDOCS_DESCRIBE_STRUCT(Tag, (), ())
// Empty lists give an empty base list and no member descriptors.
static_assert(std::is_class_v<Tag> || std::is_union_v<Tag>, "...");
[[maybe_unused]]
typename bases_descriptor_impl<Tag, list<>>::type
mrdocs_base_descriptor_fn(Tag**);
[[maybe_unused]]
decltype(member_descriptor_fn_impl(0))
mrdocs_member_descriptor_fn(Tag**);
Afterwards the describe queries work on the type. Each member descriptor has a static pointer and name, and each base descriptor has a type alias:
namespace describe = mrdocs::describe;
static_assert(describe::described<geo::Point>);
static_assert(describe::describedMemberCount<geo::Point>() == 3);
void
print(geo::Point const& p)
{
// Own members only: x, y
describe::for_each(
describe::describe_members<geo::Point>{},
[&](auto d) {
std::cout << d.name << " = " << p.*d.pointer << '\n';
});
// Direct bases only: Shape
describe::for_each(
describe::describe_bases<geo::Point>{},
[](auto d) {
using Base = typename decltype(d)::type;
static_assert(std::is_same_v<Base, geo::Shape>);
});
// Inherited members too: name, x, y
describe::for_each_member(
p,
[](std::string_view name, auto const& value) {
std::cout << name << " = " << value << '\n';
});
}
Things to keep in mind:
-
The macro ends with its own
;, so don't add another one after it. An extra;is an empty declaration that‐Wextra‐semiflags. -
It has to be in the class's own namespace. The queries find the declarations by ADL, so they don't see them anywhere else.
-
List non‐static data members by their unqualified names, up to 128 of them.
&C::mis formed at namespace scope, so private and protected members fail to compile. For those, and for class templates, useMRDOCS_DESCRIBE_CLASSinside the class instead. -
List direct bases only.
for_each_memberanddescribedMemberCountwalk up the hierarchy themselves, which requires each listed base to be described too. Listing a type that isn't a base fails astatic_assertonce the bases are used.
Parameters
Name |
Description |
C |
The class type. |
Bases |
The parenthesized list of direct base classes. |
Members |
The parenthesized list of data member names. |
Created with MrDocs