MRDOCS_DESCRIBE_STRUCT

Describe the bases and members of a class, outside its definition.

Synopsis

#define MRDOCS_DESCRIBE_STRUCT(C, Bases, Members)

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‐semi flags.

  • 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::m is formed at namespace scope, so private and protected members fail to compile. For those, and for class templates, use MRDOCS_DESCRIBE_CLASS inside the class instead.

  • List direct bases only. for_each_member and describedMemberCount walk up the hierarchy themselves, which requires each listed base to be described too. Listing a type that isn't a base fails a static_assert once 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