MRDOCS_DESCRIBE_CLASS

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

Synopsis

Declared in <mrdocs/Support/Reflection/Describe.hpp>

#define MRDOCS_DESCRIBE_CLASS(C, Bases, Members)

Description

Works like MRDOCS_DESCRIBE_STRUCT, but goes inside the class definition, after the members it names. It declares the descriptor functions as hidden friends, so it also works for class templates and for private members:


namespace app {

struct Named
{
    std::string name;
};
MRDOCS_DESCRIBE_STRUCT(Named, (), (name))

template <class T>
class Counter : public Named
{
    T value_{};
    int hits_ = 0;

public:
    explicit Counter(T v) : Named{"counter"}, value_(v) {}

    MRDOCS_DESCRIBE_CLASS(Counter, (Named), (value_, hits_))
};

class Empty
{
public:
    MRDOCS_DESCRIBE_CLASS(Empty, (), ())
};

} // namespace app

The two calls expand to roughly the following (simplified: the ::mrdocs::describe::detail:: and ::mrdocs::describe:: qualifications are dropped):


// MRDOCS_DESCRIBE_CLASS(Counter, (Named), (value_, hits_))
// Hidden friends: only ADL on a `Counter<T>**` finds them, and each
// instantiation of the template gets its own pair. Nothing calls
// them; the `{ return {}; }` bodies only keep GCC from warning about
// undefined internal functions.
friend
typename bases_descriptor_impl<Counter, list<Named>>::type
mrdocs_base_descriptor_fn(Counter**) { return {}; }

friend
decltype(member_descriptor_fn_impl(
    0,
    member_descriptor<&Counter::value_, []{ return "value_"; }>{},
    member_descriptor<&Counter::hits_, []{ return "hits_"; }>{}))
mrdocs_member_descriptor_fn(Counter**) { return {}; }

// MRDOCS_DESCRIBE_CLASS(Empty, (), ())
friend
typename bases_descriptor_impl<Empty, list<>>::type
mrdocs_base_descriptor_fn(Empty**) { return {}; }

friend
decltype(member_descriptor_fn_impl(0))
mrdocs_member_descriptor_fn(Empty**) { return {}; }

With GCC, the two friends are also wrapped in _Pragmas that silence -Wnon-template-friend for them.

Afterwards the same describe queries as for MRDOCS_DESCRIBE_STRUCT work on every instantiation, private members included:


namespace describe = mrdocs::describe;

static_assert(describe::described<app::Counter<double>>);
static_assert(describe::describedMemberCount<app::Counter<int>>() == 3);

void
dump()
{
    app::Counter<int> c(42);

    // Prints name = counter, value_ = 42, hits_ = 0
    describe::for_each_member(
        c,
        [](std::string_view name, auto const& v) {
            std::cout << name << " = " << v << '\n';
        });
}

Things to keep in mind:

  • Put it after the member declarations. The friend return types aren't a complete-class context, so a member declared below the macro isn't found ("no member named ...").

  • C names the enclosing class. In a template, the injected name (Counter) is enough; you don't need to spell Counter<T>.

  • The access section it sits in doesn't matter, since friend declarations ignore access.

  • It ends with a function body, so it needs no trailing ;.

  • The rules for Bases and Members are the same as for MRDOCS_DESCRIBE_STRUCT: direct bases only, each described too if you walk inherited members, and up to 128 data members.

Parameters

NameDescription
CThe class type.
BasesThe parenthesized list of direct base classes.
MembersThe parenthesized list of data member names.