copyleftdev

alexandrescu-modern-cpp-design

Write C++ code following Andrei Alexandrescu's Modern C++ Design principles. Emphasizes policy-based design, template metaprogramming, and type-safe generic abstractions. Use when designing flexible, reusable libraries or when compile-time computation beats runtime overhead.

copyleftdev 0 Updated 7mo ago
GitHub

Install

npx skillscat add copyleftdev/sk1llz/alexandrescu-modern-cpp-design

Install via the SkillsCat registry.

About this skill

We need to produce a 2-3 sentence plain-text summary, objective, factual, no marketing language, no superlatives, no calls to action. Must be natural prose, no bullet points, no headings, no markdown. At most 60 words. Must not wrap in quotes. Provide only the summary text. We need to explain what the skill does, what problem it solves, when to use it. 2-3 sentences, up to 60 words. Let's craft: "The skill teaches how to write C++ code that follows Andrei Alexandrescu's Modern C++ Design, using policy-based design, template metaprogramming, and type-safe generic abstractions.

SKILL.md

Andrei Alexandrescu Style Guide

Overview

Andrei Alexandrescu's "Modern C++ Design" revolutionized how we think about C++ templates. His work on Loki library and policy-based design showed that templates are not just for containers—they're a compile-time programming language.

Core Philosophy

"C++ templates are Turing-complete. Use this power wisely."

"Policy-based design: assemble types from interchangeable parts."

Alexandrescu believes in pushing computation to compile time and using the type system as a design tool, not just a safety mechanism.

Design Principles

  1. Policy-Based Design: Build classes from interchangeable policy classes that customize behavior without inheritance overhead.

  2. Compile-Time over Runtime: What can be computed at compile time should be.

  3. Type Lists and Metaprogramming: Types themselves become first-class citizens that can be manipulated.

  4. Design Patterns in Types: Classic GoF patterns implemented with zero runtime overhead.

When Writing Code

Always

  • Consider if behavior can be a compile-time policy
  • Use static_assert to document and enforce requirements
  • Prefer tag dispatching over runtime branching for type-based logic
  • Make templates SFINAE-friendly (C++11/14) or use concepts (C++20)
  • Document template requirements explicitly

Never

  • Use runtime polymorphism when static polymorphism suffices
  • Write duplicate code that differs only in types
  • Ignore compile-time computation opportunities
  • Leave template errors to become cryptic instantiation failures

Prefer

  • Policy classes over strategy pattern (no vtable)
  • Type traits over runtime type checking
  • constexpr functions over template metafunctions (modern C++)
  • Concepts over SFINAE (C++20)
  • Variadic templates over recursive type lists (modern C++)

Code Patterns

Policy-Based Design

// Traditional OOP: Runtime overhead, fixed at compile time anyway
class Widget : public ICreationPolicy, public IThreadingPolicy { /* ... */ };

// Policy-Based: Zero overhead, infinitely configurable
template <
    class CreationPolicy,
    class ThreadingPolicy = SingleThreaded,
    class CheckingPolicy = NoChecking
>
class SmartPtr : public CreationPolicy, 
                 public ThreadingPolicy,
                 public CheckingPolicy {
    // Policies are mixed in, no vtable
};

// Usage: Configure at compile time
using ThreadSafePtr = SmartPtr<HeapCreation, MultiThreaded, AssertCheck>;
using FastPtr = SmartPtr<HeapCreation, SingleThreaded, NoChecking>;

// Policies are just classes with required interface
struct HeapCreation {
    template<class T>
    static T* Create() { return new T; }
    
    template<class T>
    static void Destroy(T* p) { delete p; }
};

struct SingleThreaded {
    struct Lock {
        Lock() = default;  // No-op
    };
};

struct MultiThreaded {
    struct Lock {
        Lock() { /* acquire mutex */ }
        ~Lock() { /* release mutex */ }
    };
};

Type Traits and SFINAE

// Type trait: Does T have a serialize() method?
template<typename T, typename = void>
struct has_serialize : std::false_type {};

template<typename T>
struct has_serialize<T, 
    std::void_t<decltype(std::declval<T>().serialize())>
> : std::true_type {};

// Use it for conditional behavior
template<typename T>
auto save(const T& obj) -> std::enable_if_t<has_serialize<T>::value> {
    obj.serialize();
}

template<typename T>
auto save(const T& obj) -> std::enable_if_t<!has_serialize<T>::value> {
    default_serialize(obj);
}

// C++20: Much cleaner with concepts
template<typename T>
concept Serializable = requires(T t) {
    { t.serialize() } -> std::convertible_to<std::string>;
};

void save(Serializable auto const& obj) {
    obj.serialize();
}

Compile-Time Type Lists (Classic Alexandrescu)

// Type list: A compile-time list of types
template<typename... Ts>
struct TypeList {};

// Operations on type lists
template<typename List>
struct Length;

template<typename... Ts>
struct Length<TypeList<Ts...>> {
    static constexpr size_t value = sizeof...(Ts);
};

// Get type at index
template<size_t I, typename List>
struct TypeAt;

template<typename Head, typename... Tail>
struct TypeAt<0, TypeList<Head, Tail...>> {
    using type = Head;
};

template<size_t I, typename Head, typename... Tail>
struct TypeAt<I, TypeList<Head, Tail...>> {
    using type = typename TypeAt<I - 1, TypeList<Tail...>>::type;
};

// Usage
using MyTypes = TypeList<int, double, std::string>;
static_assert(Length<MyTypes>::value == 3);
using Second = TypeAt<1, MyTypes>::type;  // double

Visitor Pattern via Templates

// Traditional visitor: Virtual dispatch at every node
// Alexandrescu approach: Static visitor with type list

template<typename... Types>
class Variant;

template<typename Visitor, typename Variant>
auto visit(Visitor&& v, Variant&& var) {
    return var.visit(std::forward<Visitor>(v));
}

// Modern C++ (std::variant does this)
using Value = std::variant<int, double, std::string>;

auto result = std::visit(overloaded{
    [](int i) { return std::to_string(i); },
    [](double d) { return std::to_string(d); },
    [](const std::string& s) { return s; }
}, value);

// The 'overloaded' helper (Alexandrescu-style)
template<class... Ts> 
struct overloaded : Ts... { 
    using Ts::operator()...; 
};
template<class... Ts> 
overloaded(Ts...) -> overloaded<Ts...>;

Mental Model

Alexandrescu thinks of C++ templates as a compile-time functional language:

  1. Types as values: Types can be computed, stored, and transformed
  2. Templates as functions: Template instantiation is function application
  3. Specialization as pattern matching: Like case statements on types
  4. Recursion for iteration: Compile-time loops via recursive templates

The D Language Connection

Alexandrescu later co-designed D, which incorporates many C++ template lessons:

  • Built-in compile-time function execution
  • String mixins for code generation
  • Better error messages for templates

These ideas now appear in modern C++ (constexpr, if constexpr, concepts).

When to Apply

Use Alexandrescu's techniques when:

  • You need maximum performance (zero runtime overhead)
  • Behavior variations are known at compile time
  • You're building a library with many configuration options
  • Type-based dispatch is frequent

Avoid when:

  • Runtime polymorphism is genuinely needed
  • Compile times are already problematic
  • Team isn't comfortable with template metaprogramming