Skip to content
C++
Idiom
since C++98
Intermediate

Mixin Pattern

Add reusable behavior via template base classes. CRTP mixins inject methods with zero overhead; C++23 deducing this offers a cleaner alternative.

Mixinsince C++98

A mixin is a template base class that injects reusable operations into its derived class by delegating back to host-supplied primitives at compile time, achieving zero-overhead behavioral composition without virtual dispatch.

Overview

A mixin adds orthogonal behavior to a class without altering that class's core type hierarchy. Unlike multiple inheritance from concrete classes, a mixin is intentionally incomplete: it delegates to the host for one or two primitive operations and builds richer behavior on top. The host supplies core data and primitives; the mixin augments them.

Two structural forms exist in C++:

CRTP mixin (inject-from-above): The mixin is a class template parameterized on the derived class. Methods downcast this to Derived& to call back into the host. This is the canonical form: the mixin inherits nothing but injects methods downward.

Template inheritance mixin (wrap-from-below): The mixin is parameterized on a base class and inherits from it, forwarding constructors and adding new methods on top. Use this when you need to transparently augment a type you do not control.

Both are zero-overhead. The compiler sees all types at instantiation and inlines the entire chain.

Syntax

CRTP mixin skeleton:

cpp
template<typename Derived>
struct MixinName {
    void added_method() {
        derived().required_primitive();  // delegates to Derived
    }
private:
    Derived&       derived()       { return static_cast<Derived&>(*this); }
    const Derived& derived() const { return static_cast<const Derived&>(*this); }
};

struct HostClass : MixinName<HostClass> {
    void required_primitive() { /* ... */ }
};

Template inheritance mixin skeleton:

cpp
template<typename Base>
struct MixinName : Base {
    using Base::Base;  // C++11: inherit all Base constructors

    void added_method() { /* augments inherited Base members */ }
};

using EnrichedType = MixinName<OriginalClass>;

Examples

Operator injection

Define operator< on the host; the mixin provides the rest of the comparison family at zero cost.

cpp
// Useful in C++98–C++17; C++20 operator<=> often eliminates the need
template<typename Derived>
struct Comparable {
    bool operator> (const Derived& o) const { return o   < derived(); }
    bool operator<=(const Derived& o) const { return !(o < derived()); }
    bool operator>=(const Derived& o) const { return !(derived() < o); }
    bool operator==(const Derived& o) const {
        return !(derived() < o) && !(o < derived());
    }
    bool operator!=(const Derived& o) const { return !(derived() == o); }
private:
    const Derived& derived() const { return static_cast<const Derived&>(*this); }
};

struct Version : Comparable<Version> {
    int major, minor;
    bool operator<(const Version& o) const {
        return major < o.major || (major == o.major && minor < o.minor);
    }
};

Version v1{1, 2}, v2{1, 3};
static_assert(v1 <= v2);  // resolved through mixin

C++20 note: auto operator<=>(const Version&) const = default; replaces this mixin entirely when member-wise ordering suffices. Retain the pattern only when ordering logic is non-trivial.

StrongType with variadic operation mixins

This is the pattern's production-grade showcase: a strongly-typed scalar whose capabilities are assembled at the call site, with no overhead for operations not opted into.

cpp
// Hidden-friend operator; Derived deduced from CRTP
template<typename Derived>
struct Addable {
    friend Derived operator+(Derived lhs, const Derived& rhs) {
        lhs.value += rhs.value;  // requires Derived::value
        return lhs;
    }
};

template<typename Derived>
struct Scalable {
    friend Derived operator*(Derived lhs, double factor) {
        lhs.value *= factor;
        return lhs;
    }
};

template<typename Derived>
struct Printable {
    void print() const {
        std::println("{}", derived().value);  // C++23: std::println
    }
private:
    const Derived& derived() const { return static_cast<const Derived&>(*this); }
};

// Variadic template inheritance: C++11 parameter packs + using-declarations
template<typename T, typename Tag, template<typename> class... Skills>
struct StrongType : Skills<StrongType<T, Tag, Skills...>>... {
    explicit StrongType(T v) : value{v} {}
    T value;
};

using Meters  = StrongType<double, struct MetersTag,  Addable, Scalable, Printable>;
using Seconds = StrongType<double, struct SecondsTag, Printable>;  // no arithmetic

Meters a{3.0}, b{1.5};
(a + b).print();       // 4.5 — Addable injected operator+
(a * 2.0).print();     // 6.0 — Scalable injected operator*
// a + Seconds{1.0};   // compile error — incompatible types

C++20 concepts to express mixin requirements

Pre-C++20 mixins fail silently with impenetrable template errors when the host is missing a required method. Concepts make the contract explicit.

cpp
template<typename T>
concept StringConvertible = requires(const T& t) {
    { t.to_string() } -> std::convertible_to<std::string>;
};

template<typename Derived>
    requires StringConvertible<Derived>  // C++20
struct Loggable {
    void log(std::string_view action) const {
        std::println("[{}] {}", action, derived().to_string());
    }
private:
    const Derived& derived() const { return static_cast<const Derived&>(*this); }
};

struct Order : Loggable<Order> {
    int id; double amount;
    std::string to_string() const {
        return std::format("Order({}, {:.2f})", id, amount);  // C++20
    }
};

// struct BadOrder : Loggable<BadOrder> {};
// ^ error: 'BadOrder' does not satisfy 'StringConvertible' — one clear line

C++23: deducing this eliminates the CRTP boilerplate

C++23 explicit object parameters let the mixin deduce the concrete derived type at the call site, removing the template parameter, the static_cast, and the private derived() helper entirely.

cpp
struct Comparable23 {
    // C++23: explicit object parameter deduces concrete derived type
    bool operator>(this const auto& self, const auto& o)  { return o < self; }
    bool operator<=(this const auto& self, const auto& o) { return !(o < self); }
    bool operator>=(this const auto& self, const auto& o) { return !(self < o); }
    bool operator!=(this const auto& self, const auto& o) { return !(self == o); }
};

struct Version23 : Comparable23 {
    int major, minor;
    bool operator<(const Version23& o) const {
        return major < o.major || (major == o.major && minor < o.minor);
    }
};

No <Derived> template argument, no static_cast. For new code targeting C++23, this is the preferred form.

Template inheritance mixin

Use when augmenting a type you do not control, or when the augmentation must be applied after the fact.

cpp
template<typename Base>
struct ThreadSafe : Base {
    using Base::Base;  // C++11: forward all constructors

    template<typename F>
    auto locked(F&& f) -> std::invoke_result_t<F, Base&> {
        std::lock_guard lk{mutex_};
        return std::forward<F>(f)(static_cast<Base&>(*this));
    }

private:
    mutable std::mutex mutex_;
};

struct Config {
    std::unordered_map<std::string, std::string> data;
    std::string get(const std::string& key) const {
        auto it = data.find(key);
        return it != data.end() ? it->second : std::string{};
    }
};

ThreadSafe<Config> cfg;
auto host = cfg.locked([](Config& c) { return c.get("host"); });

Best Practices

Keep mixins stateless or nearly so. A mixin carrying significant independent state is better modeled as a composed member. The mixin sweet spot is deriving richer operations from one or two host-supplied primitives, not maintaining lifecycle of its own.

Document host requirements with concepts (C++20). The implicit interface between a mixin and its host — operator<, to_string(), value — is a real contract. Express it as a concept so violations produce a single clear diagnostic.

Prefer deducing this (C++23) when available. It is strictly cleaner than CRTP for mixin purposes. Save CRTP for C++20 and earlier targets or for patterns that genuinely need the Derived type to appear in the mixin's own interface (e.g., return types).

Provide both const and non-const derived() overloads. If the mixin injects any mutating operation, you need a non-const downcast. Missing it causes the compiler to silently call the wrong overload or reject the call.

Use the hidden-friend idiom for injected operators. Defining operator+ as a friend inside the mixin keeps it in the associated namespace of Derived, avoids ADL ambiguity, and does not pollute the enclosing namespace.

Common Pitfalls

Name collisions between mixins. When multiple mixins each inject size(), hash(), or log(), the unqualified names conflict in the derived class. There is no automatic resolution. Either qualify the call, rename one mixin's method, or merge overlapping mixins.

Forgetting using Base::Base in template inheritance mixins. Without constructor inheritance (C++11), the wrapper has no constructors matching the base. Objects default-initialize silently, which is almost never the intent.

The diamond still occurs. If two CRTP mixins share a common base — e.g., both Serializable<D> and Hashable<D> inherit from StringConvertible<D> — the diamond problem resurfaces. Use virtual inheritance on the shared base, or refactor so no two mixins inherit from the same template.

Applying Comparable when <=> suffices. In C++20, auto operator<=>(const T&) const = default; synthesizes all six comparison operators from a member-wise three-way comparison. Reaching for the Comparable mixin for such types is unnecessary complexity.

Incomplete mixin declarations shipped in headers. If a mixin method is declared but not defined (e.g., serialize() without a body), the error surfaces only when the method is called — potentially in a completely different translation unit, with a confusing linker message.

CRTP Mixins (Curiously Recurring Template Pattern)

CRTP feeds the derived class back to the base as a template parameter, enabling static dispatch with zero virtual overhead. The base receives Derived at instantiation time, so every call through static_cast<Derived&>(*this) is resolved at compile time and inlined.

cpp
// C++11
template<typename Derived>
class Comparable {
public:
    bool operator<(const Derived& rhs) const {
        return derived().compare(rhs) < 0;  // delegates to Derived::compare
    }
    bool operator==(const Derived& rhs) const { return derived().compare(rhs) == 0; }
    bool operator!=(const Derived& rhs) const { return !(*this == rhs); }
    bool operator>(const Derived& rhs) const  { return rhs < derived(); }
    bool operator<=(const Derived& rhs) const { return !(derived() > rhs); }
    bool operator>=(const Derived& rhs) const { return !(derived() < rhs); }
private:
    const Derived& derived() const { return static_cast<const Derived&>(*this); }
};

class MyType : public Comparable<MyType> {
public:
    explicit MyType(int v) : value_{v} {}
    int compare(const MyType& rhs) const { return value_ - rhs.value_; }
private:
    int value_;
};

MyType a{1}, b{2};
static_assert(a < b);   // resolved through Comparable<MyType>::operator<
static_assert(b > a);   // resolved through Comparable<MyType>::operator>

CRTP mixins offer two concrete advantages over virtual dispatch: the call carries no vtable pointer dereference, and the derived type is known at compile time so the body can be inlined and the optimizer can reason across the boundary. The cost is one extra template instantiation per host type, which is paid at compile time only.

Mixin with Concepts Constraints (C++20)

Concept constraints on mixin template parameters turn a silent substitution failure into a single, readable diagnostic at the point of misuse. Without a constraint, a missing name() method produces a cascade of error notes deep inside the mixin body; with one, the compiler rejects the instantiation at the class definition with a clear message.

cpp
// C++20
template<typename T>
concept HasName = requires(T t) {
    { t.name() } -> std::convertible_to<std::string>;
};

template<HasName Base>
class DisplayMixin : public Base {
public:
    using Base::Base;

    void display() const {
        std::println("Entity: {}", Base::name());  // C++20: std::println via <print>
    }

    std::string badge() const {
        return "[" + Base::name() + "]";
    }
};

class Person {
public:
    explicit Person(std::string n) : name_{std::move(n)} {}
    std::string name() const { return name_; }
private:
    std::string name_;
};

class Employee : public DisplayMixin<Person> {
public:
    using DisplayMixin::DisplayMixin;  // forward Person's constructor
};

Employee e{"Alice"};
e.display();   // prints: Entity: Alice
// class Broken : public DisplayMixin<int> {};
// ^ error: 'int' does not satisfy 'HasName' — one clear line

Applying HasName on the template parameter rather than inside the body means the constraint is checked once, at the class template specialization boundary, before any member function is instantiated.

Policy-Based Design: Mixins as Policies

Policy-based design decomposes a class into independent behavioral axes — storage, serialization, threading — each supplied as a separate compile-time mixin. Each policy contributes its interface through inheritance; the host class combines them via a variadic base list and picks up all their methods without any virtual dispatch.

cpp
// C++11
struct FileStorage {
    void save(std::string_view path, std::string_view data) {
        // write data to path on disk
    }
    std::string load(std::string_view path) {
        // read and return data from path
        return {};
    }
};

struct MemoryStorage {
    void save(std::string_view, std::string_view data) { buffer_ = data; }
    std::string load(std::string_view) { return buffer_; }
private:
    std::string buffer_;
};

struct JsonSerializer {
    std::string serialize(std::string_view content) const {
        return std::string{R"({"content":")"} + std::string{content} + "\"}";
    }
};

struct XmlSerializer {
    std::string serialize(std::string_view content) const {
        return "<doc>" + std::string{content} + "</doc>";
    }
};

template<typename StoragePolicy, typename SerializationPolicy>
class Document : StoragePolicy, SerializationPolicy {
public:
    void write(std::string_view path, std::string_view content) {
        StoragePolicy::save(path, SerializationPolicy::serialize(content));
    }
    std::string read(std::string_view path) {
        return StoragePolicy::load(path);
    }
};

using PersistentJsonDoc  = Document<FileStorage,   JsonSerializer>;
using InMemoryXmlDoc     = Document<MemoryStorage, XmlSerializer>;

PersistentJsonDoc doc;
doc.write("output.json", "hello world");

Policies are chosen at compile time: swapping FileStorage for MemoryStorage costs nothing at runtime and produces a completely independent binary. A virtual-strategy version pays a pointer dereference and an indirect call on every operation. The trade-off is that policy selection cannot be deferred until runtime; when you need that flexibility, prefer a virtual strategy injected by the constructor.

Mixin with using Declarations for Name Resolution

When two mixins each inject a method with the same name, the derived class must bring both into scope explicitly with using declarations, or the unqualified name is ambiguous and the build fails. The using declarations act as overload-set mergers: the compiler sees one combined set of candidates for the name and picks the best match by the usual rules.

cpp
// C++11
template<typename Derived>
struct Serializable {
    std::string process(std::string_view mode) const {
        return std::string{"serialize:"} + std::string{mode};
    }
};

template<typename Derived>
struct Compressible {
    std::string process(std::string_view mode) const {
        return std::string{"compress:"} + std::string{mode};
    }
};

struct Payload
    : Serializable<Payload>
    , Compressible<Payload>
{
    // Without these, any call to process() is ambiguous:
    using Serializable<Payload>::process;
    using Compressible<Payload>::process;

    // Now both overloads are visible; callers pick by argument type / overload res.
};

Payload p;
// Both are now callable with full overload resolution:
p.Serializable<Payload>::process("json");    // explicit qualification always works
p.Compressible<Payload>::process("gzip");

This is fundamentally different from virtual overriding: virtual dispatch selects one final overrider at runtime; using-based merging makes multiple base-class overloads simultaneously visible for compile-time overload resolution. If you need both to be callable without qualification, ensure their signatures differ so the call is unambiguous; if they are identical, one using declaration hides the other and you must qualify.

EBO (Empty Base Optimization) with Mixins

Inheriting from an empty mixin adds zero bytes to the derived object size — a guarantee in the C++ standard — whereas a composed empty member always occupies at least one byte. This matters for allocator mixins, no-op logger mixins, and policy classes that carry no state: you get the behavioral injection without any padding cost.

cpp
// C++11 — EBO applies automatically when the base is empty
struct EmptyLogger {
    void log(std::string_view) const {}  // no-op; no data members
};

struct ActiveLogger {
    void log(std::string_view msg) const { std::println("{}", msg); }
};

template<typename Logger>
struct Connection : Logger {
    using Logger::Logger;
    void connect(std::string_view host) {
        Logger::log("connecting");
        host_ = host;
    }
    std::string host_;
};

// EBO: Connection<EmptyLogger> has same size as a plain string
static_assert(sizeof(Connection<EmptyLogger>) == sizeof(std::string));

// Without EBO (composition instead):
struct ConnectionComposed {
    EmptyLogger logger_;  // occupies 1 byte + possible padding
    std::string host_;
};
// sizeof(ConnectionComposed) > sizeof(std::string) due to padding

C++20 introduces [[no_unique_address]] as the composition equivalent, instructing the compiler to apply the same optimization to a non-static data member rather than a base class.

cpp
// C++20
struct ConnectionFlat {
    [[no_unique_address]] EmptyLogger logger_;  // zero overhead, like EBO
    std::string host_;
};

static_assert(sizeof(ConnectionFlat) == sizeof(std::string));

Use EBO (inheritance) when the empty type is a true behavioral mixin with no independent identity. Use [[no_unique_address]] when you prefer composition for other design reasons (e.g., the type is final, or you want named access via logger_.log()).

Mixin vs Inheritance vs Composition: Decision Guide

Choosing the right mechanism depends on whether you need runtime polymorphism, zero-size guarantees, or maximum testability. The table below summarizes the trade-offs across the four main approaches in modern C++.

ApproachRuntime polymorphismCompile-time dispatchEmpty base optName hiding riskCode reuseTestability
Virtual inheritanceYesNoNoLowHighHigh (mock via vtable)
CRTP mixinNoYesYesMediumHighMedium (template instantiation)
Policy classNoYesYesLow (qualified)HighMedium (swap policy type)
Composition + [[no_unique_address]]NoYesYes (C++20)NoneMediumHigh (inject via constructor)

Mixins work best for stateless behaviors: comparison operators, serialization hooks, logging shims, hash or print adapters. They inject a rich interface from one or two host primitives at zero runtime cost. Composition is better for stateful dependencies — a database connection, a clock, a cache — where the dependency has its own lifecycle, where you want to swap implementations at runtime, or where you need to mock the collaborator in a test without recompiling the template.

See Also

  • CRTP — the broader pattern; CRTP mixins are one application of it
  • Policy-Based Design — related compile-time composition using template parameters rather than inheritance
  • Type Erasure — the runtime dual: wrapping heterogeneous behavior without exposing the concrete type