[#MRDOCS_DESCRIBE_KINDS] = MRDOCS_DESCRIBE_KINDS :mrdocs: Describe a polymorphic base and the closed set of its derived classes. == Synopsis Declared in `<mrdocs/Support/Reflection/Describe.hpp>` [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- #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: [,cpp] ---- 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): [,cpp] ---- // MRDOCS_DESCRIBE_KINDS(Shape, Circle, Square) static_assert(std::is_class_v, "..."); // 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{}, kind_descriptor{})) mrdocs_kind_descriptor_fn(Shape**) { return {}; } // So describe_kinds is // list, // kind_descriptor> // and each descriptor's `type` alias names one derived class. // MRDOCS_DESCRIBE_KINDS(One, OnlyChild) static_assert(std::is_class_v, "..."); [[maybe_unused]] inline decltype(kind_descriptor_fn_impl( 0, kind_descriptor{})) mrdocs_kind_descriptor_fn(One**) { return {}; } // MRDOCS_DESCRIBE_KINDS(Leaf): an empty kind list static_assert(std::is_class_v, "..."); [[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`: [,cpp] ---- namespace describe = mrdocs::describe; static_assert(describe::has_describe_kinds::value); double area(shapes::Shape const& s) { return mrdocs::visit(s, [](T const& shape) -> double { if constexpr (std::is_same_v) return 3.14159 * shape.radius * shape.radius; else return shape.side * shape.side; }); } int countKinds() { int n = 0; describe::for_each( describe::describe_kinds{}, [&](auto d) { using D = typename decltype(d)::type; static_assert(std::is_base_of_v); ++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 [cols="1,4"] |=== | Name| Description | *C* | The class type whose kinds follow. |=== [.small]#Created with https://www.mrdocs.com[MrDocs]#