MRDOCS_DESCRIBE_KINDS
Describe a polymorphic base and the closed set of its derived classes.
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::visitor in afor_eachbody that touchesD, 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::visitneeds 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 isinline, 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
.incfile, useMRDOCS_DESCRIBE_KINDS_BEGINandMRDOCS_DESCRIBE_KINDS_ENDinstead.
Parameters
Name |
Description |
C |
The class type whose kinds follow. |
Created with MrDocs