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.

CRTP as a Zero-Cost Template Method

Claim. When the concrete type is always known at compile time, CRTP eliminates vtable overhead entirely — every hook call inlines to a direct function call with no indirection.

cpp
// C++11
template<typename Derived>
class Algorithm {
public:
    void run() {
        self().step1();
        self().step2();
        self().step3();
    }

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

class FastCodec : public Algorithm<FastCodec> {
    friend class Algorithm<FastCodec>;

    void step1() { /* read input buffer  */ }
    void step2() { /* transform in-place */ }
    void step3() { /* write output       */ }
};

The compiler sees through static_cast<Derived&>(*this) and replaces each self().stepN() with a direct call — or eliminates the call frame altogether with inlining. Virtual dispatch, by contrast, must dereference the vtable pointer, load the function pointer, and perform an indirect call that most branch predictors handle poorly in polymorphic call sites.

cpp
// C++11 — assembly comparison (conceptual)
// CRTP:    call FastCodec::step1   (resolved at compile time, inlined)
// Virtual: mov rax, [rdi]          ; load vptr
//          call [rax + offset]     ; indirect call — not inlined

Use this pattern for performance-critical algorithms — parsers, codecs, image filters, signal processing pipelines — where even a few nanoseconds per iteration accumulates over millions of calls. The cost: no heterogeneous collections, and each instantiation is a distinct type.

Template Method with std::function Hooks

Claim. Replacing virtual hooks with std::function data members trades a small amount of call-site overhead for maximum flexibility: hooks can be set, replaced, or composed at runtime without subclassing.

cpp
// C++11
#include <functional>
#include <string>

struct Token { std::string text; int kind; };
struct Context { std::string source; std::size_t pos; };

class Parser {
public:
    std::function<void(Token)>   on_token;    // nullable hook
    std::function<void(Context&)> on_error;

    void parse(Context& ctx) {
        tokenize(ctx);          // skeleton step — always runs
        while (auto tok = next_token(ctx)) {
            if (on_token) on_token(*tok);   // call hook if set
        }
    }

private:
    void tokenize(Context& ctx) { /* lex ctx.source */ }
    std::optional<Token> next_token(Context& ctx);  // C++17
};

// Usage — hooks set at runtime, no subclass needed
Parser p;
p.on_token = [](Token t) { std::cout << t.text << '\n'; };
p.on_error = [](Context& c) { throw std::runtime_error{c.source}; };

Virtual hooks are faster — a vtable call costs one pointer dereference versus std::function's potential heap allocation and type-erased indirect call. Choose std::function hooks when: the hook must be swapped after construction, multiple independent hooks are composed at runtime, or the caller lives in a context where subclassing is awkward (lambdas, test code, scripting bridges).

cpp
// C++11 — swapping a hook at runtime (impossible with virtual override)
p.on_token = [&log](Token t) { log.record(t); };  // replace mid-run

Protected vs Private Virtual in Template Method

Claim. The Non-Virtual Interface idiom deliberately uses private virtual hooks so derived classes can customize steps but cannot call them directly, preserving the base class's ordering and pre/post-condition enforcement.

cpp
// C++11
class ReportJob {
public:
    // NVI skeleton: non-virtual, enforces pre/post conditions
    void execute() {
        lock_.lock();
        log("execute: start");
        do_execute();           // private virtual hook
        log("execute: end");
        lock_.unlock();
    }

    virtual ~ReportJob() = default;

private:
    virtual void do_execute() = 0;   // private — derived cannot call directly

    void log(std::string_view msg);  // C++17
    std::mutex lock_;
};

class HtmlReport : public ReportJob {
private:
    void do_execute() override {     // override is legal even though hook is private in base
        // render HTML — lock/log/timing are handled by base
    }
};

Scott Meyers (Effective C++, Item 35) recommends this form: the public interface is entirely non-virtual, and every customization point is a private virtual. Benefits: logging, timing, locking, and input validation in execute() cannot be bypassed by any derived class, regardless of how deep the hierarchy runs.

cpp
// C++11 — protected virtual: allows derived to call the hook themselves
class ExtendableJob : public ReportJob {
protected:
    void do_execute() override {
        pre_step();
        // Could call do_execute() on a sub-subclass — requires protected
    }
    virtual void pre_step() {}
};

Promote a hook from private to protected only when a sub-subclass legitimately needs to invoke it via Base::do_execute() to extend behavior rather than replace it.

Composing Template Methods: Algorithm Chains

Claim. A pipeline of Template Method stages — each enforcing its own invariants — composes cleanly when the output of one stage is the typed input of the next, and cross-cutting concerns (timing, logging) attach at the base level without touching derived classes.

cpp
// C++11
#include <chrono>
#include <string>
#include <vector>

struct RawData   { std::vector<std::byte> bytes; };
struct ParsedDoc { std::string title; std::vector<std::string> lines; };
struct Report    { bool valid; std::string summary; };

class Loader {
public:
    RawData load(std::string_view path) {
        auto t0 = std::chrono::steady_clock::now();
        auto result = do_load(path);
        auto t1 = std::chrono::steady_clock::now();
        record_timing("load", t1 - t0);
        return result;
    }
    virtual ~Loader() = default;
private:
    virtual RawData do_load(std::string_view path) = 0;
    void record_timing(std::string_view stage,
                       std::chrono::steady_clock::duration d);
};

class Parser {
public:
    ParsedDoc parse(const RawData& raw) {
        auto t0 = std::chrono::steady_clock::now();
        auto result = do_parse(raw);
        auto t1 = std::chrono::steady_clock::now();
        record_timing("parse", t1 - t0);
        return result;
    }
    virtual ~Parser() = default;
private:
    virtual ParsedDoc do_parse(const RawData& raw) = 0;
    void record_timing(std::string_view stage,
                       std::chrono::steady_clock::duration d);
};

class Validator {
public:
    Report validate(const ParsedDoc& doc) {
        auto t0 = std::chrono::steady_clock::now();
        auto result = do_validate(doc);
        auto t1 = std::chrono::steady_clock::now();
        record_timing("validate", t1 - t0);
        return result;
    }
    virtual ~Validator() = default;
private:
    virtual Report do_validate(const ParsedDoc& doc) = 0;
    void record_timing(std::string_view stage,
                       std::chrono::steady_clock::duration d);
};

Each stage is independently testable: inject a Loader that returns canned bytes, a Parser that returns a fixed ParsedDoc, and a Validator that checks only structure. Timing and logging accumulate in the base without any derived class knowing. The pattern scales to arbitrarily deep pipelines because each base class owns exactly one skeleton and one set of invariants.

Template Method in std: sort, unique_copy, transform

Claim. The STL algorithm library is Template Method via function objects: the skeleton (iteration order, comparison protocol, memory model) is fixed in the standard library implementation, and the customization point is the callable you supply.

std::sort(begin, end, comp) is structurally identical to NVI's generate() calling write_body(). The sort skeleton — partition selection, recursion, swap logic — is invariant. The comparison step is your hook.

cpp
// C++11
#include <algorithm>
#include <vector>
#include <functional>

std::vector<int> v{5, 2, 8, 1, 9};

// Default hook: operator<
std::sort(v.begin(), v.end());

// Custom hook: reverse order
std::sort(v.begin(), v.end(), std::greater<int>{});

// Lambda hook: sort by absolute value
std::sort(v.begin(), v.end(),
    [](int a, int b) { return std::abs(a) < std::abs(b); });

Applying the same thinking to your own algorithms makes customization explicit and composable:

cpp
// C++20 — Concepts make the customization point's contract explicit
#include <concepts>

template<typename T>
concept Scorable = requires(T a, T b) {
    { a.score() } -> std::convertible_to<double>;
};

template<Scorable T, typename Comp = std::less<double>>
void rank(std::vector<T>& items, Comp cmp = {}) {
    // skeleton: stable sort by score
    std::stable_sort(items.begin(), items.end(),
        [&](const T& a, const T& b) { return cmp(a.score(), b.score()); });
}

Concepts (C++20) formalize the hook's contract at the call site rather than via documentation or runtime errors, making accidental misuse a compile error instead of undefined behavior.

Testing Template Method Implementations

Claim. The NVI idiom makes Template Method implementations testable without mocking frameworks: override the private virtual hooks in a thin test subclass that records calls, then assert on the recording.

cpp
// C++11
#include <vector>
#include <string>

class DataProcessor {
public:
    void process(const std::vector<double>& data) {
        auto cleaned   = do_clean(data);
        auto validated = do_validate(cleaned);
        if (validated) do_emit(cleaned);
    }
    virtual ~DataProcessor() = default;

private:
    virtual std::vector<double> do_clean(const std::vector<double>& d) { return d; }
    virtual bool do_validate(const std::vector<double>& d) { return !d.empty(); }
    virtual void do_emit(const std::vector<double>& d) = 0;
};

// Test subclass — no mock framework required
class TestableProcessor : public DataProcessor {
public:
    std::vector<std::string> calls;      // records invocation order
    bool validate_result = true;

private:
    std::vector<double> do_clean(const std::vector<double>& d) override {
        calls.push_back("clean");
        return d;
    }
    bool do_validate(const std::vector<double>& d) override {
        calls.push_back("validate");
        return validate_result;
    }
    void do_emit(const std::vector<double>& d) override {
        calls.push_back("emit");
    }
};

// Test: skeleton calls hooks in order, skips emit when validation fails
void test_ordering() {
    TestableProcessor p;
    p.process({1.0, 2.0, 3.0});
    assert((p.calls == std::vector<std::string>{"clean", "validate", "emit"}));

    p.calls.clear();
    p.validate_result = false;
    p.process({1.0});
    assert((p.calls == std::vector<std::string>{"clean", "validate"}));  // emit skipped
}

Contrast with dependency injection (Strategy pattern): passing a strategy object into the constructor is more testable in isolation but requires designing the interface upfront and wiring collaborators. The test-subclass approach works with any existing NVI hierarchy — no interface changes needed. Use the test-subclass technique for verifying skeleton ordering; use injected mocks when testing the hook logic itself in isolation.

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