MRDOCS_DESCRIBE_KINDS

Describe a polymorphic base and the closed set of its derived classes.

Synopsis

#define MRDOCS_DESCRIBE_KINDS(C, …​)

Description

Place it at namespace scope, in the namespace of the base. The first argument is the base; the rest are its concrete derived classes, from none up to 128:

namespace shapes {

enum class ShapeKind { Circle, Square };

struct Shape
{
    ShapeKind Kind;
};

struct Circle : Shape
{
    static constexpr ShapeKind kind_id = ShapeKind::Circle;
    double radius = 1;
    Circle() : Shape{kind_id} {}
};

struct Square : Shape
{
    static constexpr ShapeKind kind_id = ShapeKind::Square;
    double side = 2;
    Square() : Shape{kind_id} {}
};

MRDOCS_DESCRIBE_KINDS(Shape, Circle, Square)  // several kinds

struct One { int Kind; };
struct OnlyChild : One {};
MRDOCS_DESCRIBE_KINDS(One, OnlyChild)         // a single kind

struct Leaf {};
MRDOCS_DESCRIBE_KINDS(Leaf)                   // no kinds at all

} // namespace shapes

The three calls expand to roughly the following (simplified: the ::mrdocs::describe::detail:: qualification is dropped and the assertion message is shortened):

// MRDOCS_DESCRIBE_KINDS(Shape, Circle, Square)
static_assert(std::is_class_v<Shape>, "...");

// Defined inline but never called: only the return type matters.
// ADL finds it from a `Shape**`.
[[maybe_unused]]
inline decltype(kind_descriptor_fn_impl(
    0,
    kind_descriptor<Shape, Circle>{},
    kind_descriptor<Shape, Square>{}))
mrdocs_kind_descriptor_fn(Shape**) { return {}; }

// So describe_kinds<Shape> is
//   list<kind_descriptor<Shape, Circle>,
//        kind_descriptor<Shape, Square>>
// and each descriptor's `type` alias names one derived class.

// MRDOCS_DESCRIBE_KINDS(One, OnlyChild)
static_assert(std::is_class_v<One>, "...");
[[maybe_unused]]
inline decltype(kind_descriptor_fn_impl(
    0,
    kind_descriptor<One, OnlyChild>{}))
mrdocs_kind_descriptor_fn(One**) { return {}; }

// MRDOCS_DESCRIBE_KINDS(Leaf): an empty kind list
static_assert(std::is_class_v<Leaf>, "...");
[[maybe_unused]]
inline decltype(kind_descriptor_fn_impl(0))
mrdocs_kind_descriptor_fn(Leaf**) { return {}; }

Afterwards describe::has_describe_kinds is true for the base (even with no kinds, where describe::describe_kinds is an empty list), describe::for_each iterates the kinds in the listed order, and mrdocs::visit from <mrdocs/Support/TypeTraits/Visitor.hpp> can downcast a base reference by comparing its Kind member with each kind's static kind_id:

namespace describe = mrdocs::describe;

static_assert(describe::has_describe_kinds<shapes::Shape>::value);

double
area(shapes::Shape const& s)
{
    return mrdocs::visit(s, []<class T>(T const& shape) -> double {
        if constexpr (std::is_same_v<T, shapes::Circle>)
            return 3.14159 * shape.radius * shape.radius;
        else
            return shape.side * shape.side;
    });
}

int
countKinds()
{
    int n = 0;
    describe::for_each(
        describe::describe_kinds<shapes::Shape>{},
        [&](auto d) {
            using D = typename decltype(d)::type;
            static_assert(std::is_base_of_v<shapes::Shape, D>);
            ++n;
        });
    return n; // 2
}

Things to keep in mind:

  • The derived classes may be forward declarations where the macro expands. They need to be complete wherever you use them, e.g. in mrdocs::visit or in a for_each body that touches D, so a header that includes every kind's header is still the natural home for the macro.

  • The macro doesn't check that each kind derives from the base.

  • mrdocs::visit needs at least one kind, so a base described with no kinds can be queried but not visited.

  • It ends with a function body, so it needs no trailing ;. The function is inline, so the macro is safe in a header, but each base can only be described once.

  • When the kinds already live in an X‐macro .inc file, use MRDOCS_DESCRIBE_KINDS_BEGIN and MRDOCS_DESCRIBE_KINDS_END instead.

Parameters

Name

Description

C

The class type whose kinds follow.

Created with MrDocs