MRDOCS_DESCRIBE_CLASS

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

Synopsis

#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 _Pragma`s 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

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