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

Template Method Pattern

Define an algorithm's invariant skeleton in a non-virtual base-class method and defer variable steps to subclasses via NVI virtual hooks or CRTP for zero-overhead static dispatch.

Template Method Patternsince C++98

A behavioral design pattern that fixes an algorithm's invariant skeleton in a non-virtual base-class method and exposes variable steps as virtual hooks that subclasses override — enforcing ordering, preconditions, and postconditions at the base level.

Overview

The Template Method pattern separates what an algorithm does from how individual steps are performed. The base class owns the structure — ordering, invariants, error handling — while subclasses supply only the variable steps. Derived classes cannot reorder or skip steps they don't own.

Do not confuse this with C++ templates. The pattern predates the language feature and is named after the concept of a skeleton with blanks to fill in. CRTP uses C++ templates to implement a compile-time variant, but the pattern itself requires no template machinery.

Two implementation strategies:

StrategyDispatchOverheadHeterogeneous collections?
Virtual (NVI)Dynamicvtable callYes, via base pointer
CRTPStaticZero (fully inlined)No — each instantiation is a distinct type

Choose virtual when the concrete type is unknown at compile time. Choose CRTP when the type is always fixed at compile time and vtable overhead is unacceptable (inner loops, embedded systems, high-frequency paths).

Syntax

NVI: Non-Virtual Interface

The idiomatic C++ form declares the template method public and non-virtual, with hooks in private virtual slots. Private virtuals are legal and overridable in derived classes — they just cannot be called directly by those classes, which prevents subclasses from bypassing the skeleton's invariants.

cpp
class ReportGenerator {
public:
    // Template method: skeleton is invariant — cannot be overridden
    void generate() {
        validate_context();   // base enforces this always runs first
        write_header();       // hook
        write_body();         // hook — pure, subclass must provide
        write_footer();       // hook
    }

    virtual ~ReportGenerator() = default; // C++11

private:
    void validate_context() {
        if (!context_valid_) throw std::logic_error{"missing context"};
    }

    virtual void write_header() { /* default: emit document title */ }
    virtual void write_body()   = 0;   // required hook
    virtual void write_footer() {}     // optional hook — default: no-op

    bool context_valid_ = true;
};

class SalesReport : public ReportGenerator {
private:
    void write_body() override {   // C++11 override
        // emit sales data
    }
    void write_footer() override {
        // append signature block
    }
};

Use protected instead of private only when derived classes legitimately need to call the hook themselves (e.g., a subclass of a subclass extending behavior via Base::write_footer()).

CRTP: Static Template Method

cpp
template<typename Derived>
class Encoder {
public:
    // Template method: resolved entirely at compile time
    void encode(std::span<const std::byte> input) { // std::span: C++20
        auto prepared = self().prepare(input);
        auto encoded  = self().do_encode(prepared);
        self().flush(encoded);
    }

protected:
    // Optional hooks — override selectively in Derived
    std::vector<std::byte> prepare(std::span<const std::byte> in) {
        return {in.begin(), in.end()};
    }
    void flush(const std::vector<std::byte>&) {}

private:
    Derived& self() { return static_cast<Derived&>(*this); }
};

class Base64Encoder : public Encoder<Base64Encoder> {
    friend class Encoder<Base64Encoder>;
protected:
    std::vector<std::byte> do_encode(std::vector<std::byte> data) {
        // ... base64 transform
        return data;
    }
};

No vtable is generated. The compiler sees through the static_cast and inlines each hook directly, producing code identical to a sequence of direct function calls.

Examples

Data Pipeline with Enforced Invariants

cpp
#include <vector>
#include <algorithm>
#include <cmath>
#include <stdexcept>

class DataPipeline {
public:
    std::vector<double> run(std::vector<double> input) {
        if (input.empty())
            throw std::invalid_argument{"empty input"};

        auto cleaned = clean(std::move(input));

        if (!validate(cleaned))
            throw std::runtime_error{"post-clean validation failed"};

        return transform(std::move(cleaned));  // C++11 move semantics
    }

    virtual ~DataPipeline() = default;

private:
    virtual std::vector<double> clean(std::vector<double> v) const {
        // default: strip NaN values
        std::erase_if(v, [](double x) { return std::isnan(x); }); // C++20
        return v;
    }

    virtual bool validate(const std::vector<double>& v) const {
        return !v.empty();
    }

    virtual std::vector<double> transform(std::vector<double> v) const = 0;
};

class NormalizePipeline : public DataPipeline {
private:
    std::vector<double> transform(std::vector<double> v) const override {
        auto [it_min, it_max] = std::minmax_element(v.begin(), v.end()); // C++17 structured bindings
        double range = *it_max - *it_min;
        if (range == 0.0) return std::vector<double>(v.size(), 0.0);
        for (auto& x : v) x = (x - *it_min) / range;
        return v;
    }
};

The base class guarantees validate() always runs after clean() and that the empty-input check precedes any hook. NormalizePipeline cannot bypass or reorder those steps.

Game Application Loop

cpp
class GameApplication {
public:
    void run() {
        on_init();
        while (!quit_) {
            on_input();
            on_update(target_dt_);
            on_render();
        }
        on_shutdown();
    }

    virtual ~GameApplication() = default;

protected:
    bool quit_ = false;

private:
    static constexpr float target_dt_ = 1.0f / 60.0f; // C++11 constexpr

    virtual void on_init()            {}
    virtual void on_input()           {}
    virtual void on_update(float dt)  = 0;
    virtual void on_render()          = 0;
    virtual void on_shutdown()        {}
};

class SpaceGame : public GameApplication {
    float ship_x_ = 0.0f;

    void on_update(float dt) override {
        ship_x_ += 200.0f * dt;
        if (ship_x_ > 1920.0f) quit_ = true;
    }
    void on_render() override {
        // submit GPU draw calls for ship at ship_x_
    }
};

Best Practices

Prefer private hooks over protected hooks. Private virtual methods are overridable but not callable by derived classes. This enforces that subclasses customize steps without being able to invoke them out of sequence. Promote to protected only when a subclass hierarchy legitimately chains calls.

Declare the template method final when the skeleton must not change. C++11's final prevents accidental override of the invariant skeleton:

cpp
class Base {
public:
    void execute() final { step_a(); step_b(); step_c(); }
private:
    virtual void step_a() = 0;
    virtual void step_b() {}
    virtual void step_c() {}
};

Always mark overrides with override. C++11's override catches misspelled hook names and signature mismatches at compile time. Never omit it in derived classes.

Provide a virtual destructor. Any polymorphic base class whose objects may be deleted through a base pointer requires a virtual destructor. Omitting it is undefined behavior. A protected non-virtual destructor is an alternative when deletion through the base is intentionally prohibited.

Return values through hooks, not side effects. Hooks that communicate results by mutating shared state create hidden coupling between steps. Pass data explicitly via parameters and return values — the clean → validate → transform chain above demonstrates this.

Common Pitfalls

Calling virtual hooks from constructors or destructors. During base-class construction, dynamic dispatch resolves to the base implementation, never the derived override. Code expecting on_init() to call the derived on_init() from a constructor will silently call the base no-op instead.

cpp
class Broken {
public:
    Broken() { on_init(); }     // always calls Broken::on_init(), not derived
    virtual void on_init() {}
};

Use two-phase initialization: construct the object, then call the template method as a separate step.

Making the template method itself virtual. If run() or generate() is virtual, a derived class can override the entire skeleton, discarding every invariant the base enforces. The skeleton must be non-virtual for NVI to have any meaning.

Excessive hooks create fragile base class syndrome. Each hook is a coupling point. A base class with a dozen hooks effectively becomes an abstract interface with hidden ordering constraints. If most hooks must be overridden for a subclass to be correct, the abstraction is wrong — consider Strategy or policy-based design instead.

CRTP does not support runtime polymorphism. Encoder<Base64Encoder> and Encoder<GzipEncoder> are unrelated types. You cannot store them in a std::vector<Encoder*> or pass them through a common interface without adding a separate virtual base. For heterogeneous collections, use virtual dispatch.

Missing friend declarations in CRTP hooks. If do_encode is private in the derived class, self().do_encode(...) in the base fails to compile. Either make CRTP hooks protected, or add friend class Encoder<Derived> in the derived class.

See Also

  • CRTP — the static polymorphism mechanism underpinning the zero-overhead variant
  • Policy-Based Design — compile-time step customization via template parameters rather than inheritance
  • Strategy Pattern — swaps the entire algorithm rather than individual steps; prefer when the skeleton itself varies