Polymorphism choice

Design7 standards

virtual vs variant+visit vs type erasure vs CRTP.

Classic open-set polymorphism: virtual + override, owned via unique_ptr<Base>. std::function is the everyday type-erasure tool; CRTP covers static polymorphism.

struct Sink { virtual void write(Frame) = 0; virtual ~Sink() = default; };
std::vector<std::unique_ptr<Sink>> sinks;
std::function<void(Frame)> tap;           // type erasure

Generic lambdas make small hand-rolled type erasure and visitors less painful; no model change.

Closed-set polymorphism goes value-semantic: std::variant + std::visit (with the overloaded trick) replaces inheritance where the set of alternatives is known.

template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
using Event = std::variant<KeyDown, Resize, Quit>;
std::visit(overloaded{
  [](const KeyDown& k) { handle(k); },
  [](const Resize& r)  { relayout(r); },
  [](Quit)             { stop(); }
}, ev);

Concepts become the compile-time interface: constrain templates instead of inheriting from interface classes when dispatch can be static.

template <class T>
concept Sink = requires(T t, Frame f) { t.write(f); };
void pump(Sink auto& out);

Deducing this is CRTP without the CRTP: mixins read the derived type from the call site. std::move_only_function erases move-only callables.

struct Clonable {
  template <class Self>
  auto clone(this const Self& self) { return Self(self); }  // no template base
};

std::polymorphic<T> gives open-set polymorphism value semantics (deep copy through the base), and std::function_ref erases callables non-owningly at API boundaries.

struct Node { std::polymorphic<Expr> child; };  // copyable tree of Exprs
Node b = a;   // deep-copies the dynamic Expr type
C++29direction · not adopted

Direction: pattern matching (P2688) adds structural dispatch over variants and class hierarchies — a match for C++ that would absorb most visit call sites.

// proposal sketch, not adopted:
// ev match {
//   KeyDown k => handle(k);
//   Resize r  => relayout(r);
//   Quit      => stop();
// };

C++26 per the final working draft; C++29 rows are proposals and direction, not adopted features.