Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 23 – Design, idioms and maintainable C++

CRTP, mixins and static polymorphism

Use the curiously recurring template pattern (CRTP) to add operators, counters and other mixins to C++ classes, and compare it with virtual calls.

  • Advanced
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: A CRTP base knows its derived class while compiling · Three mixins give two unrelated classes the same abilities · Static polymorphism chooses while compiling, virtual calls at run time · Practise: choose static or dynamic. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Use the curiously recurring template pattern to call derived-class code from a base
  • Compose a class's behaviour from small CRTP mixins
  • Compare static polymorphism with virtual functions
  • Prevent a mixin from being attached to the wrong class
  • Choose between CRTP, deducing this and concepts for the same job

Before you start

Employee and Version are unrelated classes, and both need comparisons, a printed description and a count of live objects: written by hand, that is six sets of functions to keep in step. The curiously recurring template pattern (CRTP): A class template used as a base, to which each derived class passes itself as the template argument, as in struct Square : DescribableCrtp<Square>, so that the base can cast *this to the derived type while compiling. More in “Deducing this: explicit object parameters in C++23”, CRTP for short, writes each set once: a class derives from a base template instantiated with itself, class Employee : public KeyOrdered<Employee>, so the base knows the exact derived class. It can call that class’s member functions, take it as a parameter type and keep state for it, all without virtual functions.

mixin: A small base class that adds one capability to the classes that derive from it, such as a count of live objects, with the few members it needs, so that it can be combined with other bases (C++ Core Guidelines C.136). More in “Multiple inheritance, the diamond and virtual bases” are small CRTP bases that each add one capability, and a class collects the mixins it needs. The result is static polymorphism: Code that works with many types, where the compiler chooses which function each call reaches from the types it sees, as with templates and CRTP bases. The call can then be inlined, but objects of different classes share no common type to be stored under.: the compiler chooses which function each call reaches while it compiles, so the call can be inlined. Dynamic polymorphism, with virtual functions, makes that choice at run time, which is what lets one container hold different classes. Every program below ran with Apple clang 21 and GCC 16.

A CRTP base knows its derived class while compiling

A base template Describable<D> cannot know D’s members while D itself is still incomplete, but its member functions are compiled only when they are first used, and by then D is a complete class. So describe() can turn *this into the derived object with static_cast<const D&>(*this) and call name() and details() on it. The cast is a compile-time conversion from base to derived, which is correct because the object really is a D. Step through one call of describe() on an Employee:

Interactive · step by step How a CRTP base reaches its derived class Step through one call of describe() on an Employee. The left column is the derived class and the call, the right column the code of the base; watch where the type Employee comes from.
How a CRTP base reaches its derived class, step 6 of 6. Showing class Employee, asha.describe(), The returned text (Asha (employee 1042)), Describable<D> (D = Employee), static_cast<const D&> (gives a const Employee&), name() and details() ("Asha" and "employee 1042"). In focus: The returned text.passes itself as Druns describe()return their textclass Employee: Describable<Employee>asha.describe()asha is an EmployeeThe returned textAsha (employee 1042)Describable<D>D = Employeestatic_cast<const D&>gives a const Employee&name() and details()"Asha" and "employee 1042"formatted

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The class names itself. Employee derives from Describable<Employee>: it passes its own name as the template argument D. While the base template was written, D was only a name.
  2. Compiled on first use. The member functions of Describable<Employee> are compiled only when they are first used. By then Employee is a complete class, with name() and details() declared.
  3. asha.describe(). The call finds describe() in the Describable<Employee> part of asha. Nothing is looked up at run time: the compiler knows that asha is an Employee.
  4. The cast down. static_cast<const Employee&>(*this) turns the base part into the whole Employee. The compiler checks only that Employee derives from the base; it is right because the object really is an Employee.
  5. The derived functions. self.name() and self.details() are ordinary calls to Employee's own functions, chosen while compiling, so the compiler can inline them. No virtual function and no vtable pointer are involved.
  6. The result. std::format joins the two parts, and the call returns "Asha (employee 1042)", the text both compilers printed.

Operators work the same way: KeyOrdered<D> defines == and <=> as friends that take two const D&, so an Employee can only be compared with another Employee, and C++20 derives !=, <, <=, > and >= from those two.

Key idea

The base learns the derived class from its own template argument, and reaches it through static_cast<const D&>(*this), a cast the compiler checks against the class hierarchy only.

Checkpoint

Check: How a CRTP base reaches its derived class

  1. Question 1 of 2 When is static_cast<const D&>(*this) in a CRTP base checked, and against what?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: While compiling, only against the class hierarchy

    static_cast from base to derived is checked while compiling, against the class hierarchy only. It is correct when the object really is a D, which is what the pattern promises when each class names itself.

  2. Question 2 of 2 Where Describable<D> is written, D is not even declared, yet describe() calls self.name(). Why does that compile?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Its member functions are compiled when first used, and by then D is a complete class

    A class template's member functions are compiled only when they are first used. By the time asha.describe() is called, Employee is complete, so describe() can call its name() and details().

Three mixins give two unrelated classes the same abilities

Employee and Version share no base class of their own; each lists the three mixins it wants and provides the members they need:

Three mixins, and two classes that use them

C header · mixins.h

// mixins.h: three CRTP mixins. Each one gives a class D one capability, built from members that D provides.
#pragma once
#include <format>
#include <string>

template <class D>
class KeyOrdered {                     // needs D::key(); gives ==, !=, <, <=, > and >= between two D objects
public:
    friend bool operator==(const D& a, const D& b) { return a.key() == b.key(); }
    friend auto operator<=>(const D& a, const D& b) { return a.key() <=> b.key(); }
};

template <class D>
class Describable {                    // needs D::name() and D::details(); gives describe()
public:
    std::string describe() const {
        const D& self = static_cast<const D&>(*this);   // this object is a D, so the cast is safe
        return std::format("{} ({})", self.name(), self.details());
    }
};

template <class D>
class Counted {                        // gives D::live(): how many D objects exist right now
public:
    static int live() { return count; }

protected:
    Counted() { ++count; }
    Counted(const Counted&) { ++count; }
    Counted& operator=(const Counted&) = default;
    ~Counted() { --count; }

private:
    inline static int count = 0;       // one counter for each D: Counted<D> is a different class for every D
};

C++ · crtp.cpp

// crtp.cpp: two unrelated classes pick up comparisons, describe() and a live count from the same three mixins.
#include "mixins.h"
#include <algorithm>
#include <print>
#include <string>
#include <tuple>
#include <vector>

class Employee : public KeyOrdered<Employee>, public Describable<Employee>, public Counted<Employee> {
public:
    Employee(int id, std::string name) : id_(id), name_(std::move(name)) {}
    int key() const { return id_; }
    std::string name() const { return name_; }
    std::string details() const { return std::format("employee {}", id_); }

private:
    int id_;
    std::string name_;
};

class Version : public KeyOrdered<Version>, public Describable<Version>, public Counted<Version> {
public:
    Version(int major, int minor, int patch) : major_(major), minor_(minor), patch_(patch) {}
    std::tuple<int, int, int> key() const { return {major_, minor_, patch_}; }
    std::string name() const { return std::format("v{}.{}.{}", major_, minor_, patch_); }
    std::string details() const { return patch_ == 0 ? "feature release" : "fix release"; }

private:
    int major_, minor_, patch_;
};

int main() {
    std::vector<Version> versions{{2, 10, 1}, {2, 9, 0}, {2, 10, 0}};
    std::ranges::sort(versions);                       // uses the < that KeyOrdered<Version> provides
    for (const Version& v : versions) std::println("{}", v.describe());

    const Employee asha{1042, "Asha"};
    const Employee ravi{1007, "Ravi"};
    std::println("{} < {}: {}", asha.describe(), ravi.describe(), asha < ravi);
    std::println("live objects: {} Employee, {} Version", Employee::live(), Version::live());
    std::println("sizeof(Employee) = {}, sizeof(Version) = {}", sizeof(Employee), sizeof(Version));
}
Sorting, describing and counting through mixins Python · crtp_session.py
from session import run

# Three CRTP mixins on two unrelated classes, with both compilers.
run("""
clang++ -std=c++23 -Wall -Wextra crtp.cpp -o crtp && ./crtp
g++-16 -std=c++23 -Wall -Wextra crtp.cpp -o crtp-gcc && ./crtp-gcc
""")

Output

$ clang++ -std=c++23 -Wall -Wextra crtp.cpp -o crtp && ./crtp
v2.9.0 (feature release)
v2.10.0 (feature release)
v2.10.1 (fix release)
Asha (employee 1042) < Ravi (employee 1007): false
live objects: 2 Employee, 3 Version
sizeof(Employee) = 32, sizeof(Version) = 12
$ g++-16 -std=c++23 -Wall -Wextra crtp.cpp -o crtp-gcc && ./crtp-gcc
v2.9.0 (feature release)
v2.10.0 (feature release)
v2.10.1 (fix release)
Asha (employee 1042) < Ravi (employee 1007): false
live objects: 2 Employee, 3 Version
sizeof(Employee) = 40, sizeof(Version) = 12

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 crtp_session.py

Step through main() and watch the two counters and the order of the versions:

Interactive · step by step Three mixins at work on versions and employees Step through main() of crtp.cpp. The left column is the objects it makes, the right column the two counters and what is printed.
Three mixins at work on versions and employees, step 7 of 7. Showing std::vector<Version> (2.9.0, 2.10.0, 2.10.1), asha and ravi (employees 1042 and 1007), Object sizes (Version 12; Employee 32, 40), Counted<Version>::count (3), Counted<Employee>::count (2), Printed (2 Employee, 3 Version). In focus: Object sizes.constructors countconstructors countstd::vector<Version>2.9.0, 2.10.0, 2.10.1asha and raviemployees 1042 and 1007Object sizesVersion 12; Employee 32, 40Counted<Version>::count3Counted<Employee>::count2Printed2 Employee, 3 Version

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Three versions. The vector holds three Versions. Counted<Version> adds one for every Version built, copies included, and takes one off for every Version destroyed, so once the vector is ready its count is 3.
  2. Sorted by key(). std::ranges::sort uses the < that KeyOrdered<Version> gives. It compares the tuples that key() returns, so 2.9.0 comes before 2.10.0, as numbers do, not as text does.
  3. describe() for each. Describable<Version> reaches each Version's name() and details(): "v2.9.0 (feature release)", "v2.10.0 (feature release)", "v2.10.1 (fix release)".
  4. Two employees. asha (1042) and ravi (1007) are built. Counted<Employee> is a different class from Counted<Version>, with a counter of its own, which reaches 2.
  5. asha < ravi. KeyOrdered<Employee> compares the keys 1042 and 1007, and the line prints false. Only two Employees can be compared: the operators take const Employee&.
  6. The live counts. The line "live objects: 2 Employee, 3 Version" reads both counters: one per class, because each Counted<D> is a class of its own.
  7. No bytes for mixins. sizeof(Version) is 12, its three ints: the three empty bases take no space. An Employee is 32 bytes with libc++ and 40 with libstdc++, an int and a std::string, whose size differs between the two.

std::ranges::sort sorted the versions numerically, 2.9.0 before 2.10.0, because KeyOrdered compares the tuple that key() returns, not the printed text. describe() reached each class’s own name() and details(). The live counts are 2 and 3 because Counted<Employee> and Counted<Version> are two different classes, each with its own static data member: A data member declared static: one variable for the whole program, shared by every object of the class and stored in none of them, so sizeof does not count it. Since C++17, inline static declares and defines it inside the class. More in “C++ static members, friends and nested types”. And the mixins cost no memory: a Version is 12 bytes, exactly its three int members, because the empty bases take no space. Only Counted runs code of its own: one increment and one decrement per object.

The pattern has costs as well. Every class gets its own copy of each mixin function it uses, compiled from a header, so a mixin used by fifty classes is instantiated fifty times, and every file that includes the header compiles it again. A class that lacks a member a mixin needs, such as key(), fails to compile inside the mixin’s code, far from the class itself. Mixins pay off when several classes need the same capability and the code must be exact about their types or fast.

Checkpoint

Check: Mixins built from CRTP bases

  1. Question 1 of 2 Why did std::ranges::sort put v2.9.0 before v2.10.0?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: KeyOrdered compares the tuples that key() returns, number by number

    KeyOrdered<Version> compares two versions by their key(), a std::tuple of three ints, so they sort as numbers. As text, "2.10" would come before "2.9".

  2. Question 2 of 2 Version gains a fourth mixin, an empty CRTP base with member functions only. What does sizeof(Version) print now?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 12 bytes

    An empty base takes no space in the object, so a Version stays its three int members, 12 bytes, as it was with three mixins.

Static polymorphism chooses while compiling, virtual calls at run time

The diagram follows one call of each kind. With CRTP, the compiler sees that asha is an Employee and compiles describe() for exactly that class; with a virtual function, the program reads the object’s virtual table: A table of function addresses that GCC and Clang build once for each class with virtual functions, shared by all its objects. A virtual call reads the object's vptr, takes the function's entry from this table and jumps to it. More in “Virtual functions, override and virtual destructors” when the call happens:

With CRTP, describe() is compiled for Employee and calls its functions directly; a virtual call reads the vtable at run time.Chosen while compiling: CRTPChosen at run time: a virtual functionasha.describe()asha is an EmployeeDescribable<Employee>::describe()the base, compiled for Employee:static_cast<const Employee&>(*this)Employee::name() and details()called directly, so theycan be inlinedshape->area()shape is a const Shape*Read the object's vtable pointer,then the slot for area()Square::area()reached by an indirect call

One call chosen while compiling, one chosen at run time

Text description of the diagram

The diagram has two parts, one above the other, each a column of three boxes joined by arrows.

The upper part is titled "Chosen while compiling: CRTP". 1. The call asha.describe(), where asha is an Employee. 2. Describable<Employee>::describe(), the base compiled for Employee, which uses static_cast<const Employee&>(*this). 3. Employee::name() and details(), called directly, so they can be inlined.

The lower part is titled "Chosen at run time: a virtual function". 1. The call shape->area(), where shape is a const Shape*. 2. The program reads the object's vtable pointer, then the slot for area(). 3. Square::area(), reached by an indirect call.

Ways to share behaviour between classes
Criterion VirtualCRTPDeducing thisConcept
The function is chosen At run timeWhile compilingWhile compilingWhile compiling
A base class is needed Yes, the interfaceYes, a template of DYes, an ordinary classNo
One container for all classes Yes, through pointersNoNoNo
Its own state per class NoYesNoNo

Each column is a real alternative for a different situation. Virtual functions are the tool when code must handle objects whose class is known only at run time, such as plugins or a list of mixed shapes. CRTP fits when the class is known while compiling and the shared code needs the exact derived type. Deducing this, the explicit object parameter: A first parameter of a member function marked with this, as in int get(this const Counter& self) (C++23). It receives the object the function is called on; the function has no implicit this, and the parameter's type can be deduced like any template parameter. More in “Deducing this: explicit object parameters in C++23” of C++23, lets an ordinary base deduce the derived type at each call, which replaces many CRTP uses with simpler code. Concepts describe the members a type must have, so generic code needs no base class at all. A CRTP mixin can also be attached to the wrong class by a copy-paste slip, and that slip still compiles.

Checkpoint

Check: Static polymorphism and virtual calls

  1. Question 1 of 2 With a CRTP base, when is the function a call reaches chosen?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: While compiling

    Static polymorphism chooses the function from the static type, while compiling. Dynamic polymorphism reads the object's vtable when the call happens.

  2. Question 2 of 2 A drawing program loads shape classes from plugins at run time and keeps all the shapes in one container. Which of the four ways fits?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Virtual functions

    Classes known only at run time, such as plugins, need a choice made at run time, which is what virtual functions make. The other three ways choose while compiling.

Practise: choose static or dynamic

Ten questions on the three ideas above follow. The session starts where your answers have been weakest so far, and a wrong choice shows what it overlooked.

Practice that adapts to you

Practise this lesson

10 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 What makes a class use the curiously recurring template pattern?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: It derives from a base template instantiated with itself

    In CRTP a class derives from a base template and passes itself as the template argument, so the base knows the exact derived class while compiling.

  2. Question 2 of 10 Inside Describable<D>::describe(), the code writes static_cast<const D&>(*this). Why is that cast safe when the mixin is used as intended?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: Because the object really is a D, which derives from Describable<D>

    A class D derives from Describable<D>, so the Describable<D> part of a D object can be cast down to D. static_cast does not check anything at run time: it trusts that the object is a D, which is why naming the wrong class is dangerous.

  3. Question 3 of 10 Which of these can a CRTP base give a class that an ordinary, non-template base cannot?

    Choose every answer that is right.

    Show the answer: Question 3 of 10

    Answer:

    • A static member of its own for each derived class
    • Calls to the derived class that the compiler can inline
    • Operators whose parameters have the exact derived type

    The base knows D, so its operators can take const D&, its statics are separate for every D and its calls to D are ordinary calls. Different derived classes have different bases, though, so they share no common type that a single vector could hold.

  4. Question 4 of 10 What is a mixin in this lesson?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: A small base that adds one capability, built from members the class provides

    A mixin is a small CRTP base that adds one capability to the classes that derive from it, using the members each class provides, so a class collects the mixins it needs.

  5. Question 5 of 10 Counted<D> is the lesson's mixin. What does the last line print?

    Read the code, then choose one answer.

    struct Order : Counted<Order> {};
    struct Refund : Counted<Refund> {};
    
    Order a, b, c;
    Refund r;
    std::println("{} {}", Order::live(), Refund::live());
    Show the answer: Question 5 of 10

    Answer: 3 1

    Counted<Order> and Counted<Refund> are two different classes, each with its own static counter, so three Order objects and one Refund object give 3 and 1.

  6. Question 6 of 10 A class derives from KeyOrdered but has no key(). Where does the compiler report the error?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: Inside the mixin's code, far from the class itself

    A mixin uses members of the class that derives from it. When one is missing, the error comes from inside the mixin's code, compiled for that class, which is one of the costs of the pattern.

  7. Question 7 of 10 A comparison mixin is used by 50 classes. What does that cost?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: Its functions are instantiated for each of the 50 classes, and recompiled in every file that includes the header

    Each Base<D> is a separate class, so a mixin used by fifty classes is instantiated fifty times, and every file that includes its header compiles it again. Mixins pay off when the shared code must be exact about types or fast.

  8. Question 8 of 10 What makes polymorphism static?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: The function to call is chosen by the compiler from the static type of the expression

    With static polymorphism the compiler knows which function a call means while it compiles, so the call can be direct and inlined. Dynamic polymorphism chooses at run time, from the object's vtable.

  9. Question 9 of 10 What does deducing this, the explicit object parameter of C++23, let an ordinary base do?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: Deduce the derived type at each call, with no template argument on the base

    With an explicit object parameter, a member function of an ordinary base can deduce the derived type at each call, which replaces many uses of CRTP with simpler code.

  10. Question 10 of 10 Generic code only needs each type to have a key() function. Which way needs no base class at all?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: A concept

    Concepts describe the members a type must have, so generic code needs no base class at all; the other three ways all start from a base.

CRTP mistakes reproduced, and its speed measured against virtual calls Pro: opens with a Pro, Premium or Ultimate pass

Two things decide whether CRTP is worth it in a code base: how it fails when it is misused, and what it buys at run time. This part reproduces the most common slip and turns it into a compile error, then reads the code that both compilers generate for a CRTP call and a virtual call, and times them.

In this part:

  • A mixin attached to the wrong class, reproduced and then refused by the compiler
  • What a CRTP call compiles to, measured against a virtual call

1 diagram · 5 runnable examples (C header, C++, C++ (wrong_base.cpp), C++ (wrong_base_guarded.cpp), Python) · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

CRTP, deducing this or a concept: one mixin three ways, then an exercise Pro: opens with a Pro, Premium or Ultimate pass

C++23 and C++20 offer two alternatives to a CRTP base, and each one changes what the compiler checks and reports. This part writes the same comparison mixin three ways, compares the errors each one gives, shows the job that only CRTP can do, and ends with a refactoring exercise.

In this part:

  • The same comparison mixin written three ways
  • What each way reports when a class forgets key()
  • Where CRTP is still the only tool: per-class state and constructors
  • Your turn: refactor Incrementable to deducing this

1 diagram · 6 runnable examples (C header, C++, Python) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

A warm-up answered in full, then three design questions about a wrong base, a library’s interface and C++23:

  1. Fresher to mid · Warm-up

    What is CRTP, and what does it give you that a virtual function does not?

    CRTP, the curiously recurring template pattern, is a class that derives from a base template instantiated with itself, as in class Employee : public KeyOrdered<Employee>. The base then knows the exact derived type: it can call the derived class's member functions through static_cast<const D&>(*this), declare operators that take const D&, and keep a separate static member for every derived class. Each call is chosen while compiling, so the compiler can inline it, and the object needs no vtable pointer. What it gives up is run-time choice: each derived class has a different base, so there is no common type through which one container could hold Employee and Version objects together. For that, virtual functions are still the tool.

  2. Mid to senior

    A class derived from the wrong instantiation of a CRTP base, Receipt : Describable<Invoice>, compiles and prints another class's data. Explain why it compiles, and how you would make that mistake a compile error wherever the base is used.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Senior to staff

    You are designing the shape interface of a geometry library used by other teams. When would you expose it as a CRTP base, and when as an abstract class with virtual functions? Give the costs you would weigh, with numbers where you can.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    A CRTP base gives every derived class its own instance counter and a clone() that returns std::unique_ptr<D>. The team wants to replace it with a C++23 base that uses explicit object parameters. Which parts can move, and which cannot? Explain each.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • In CRTP, a class derives from a base template instantiated with itself, so the base can use the exact derived type: static_cast<const D&>(*this) reaches its members, and its operators can take const D&.
  • Mixins are small CRTP bases that each add one capability; empty mixins add no bytes to the object, and each Base<D> is a separate class, so it can keep separate static data for every derived class.
  • Static polymorphism chooses the function while compiling, which allows inlining; virtual functions choose at run time, which allows one container for many classes.
  • Deducing this and concepts do many CRTP jobs with simpler code, and virtual functions remain the tool for classes known only at run time.

Terms in this lesson

static polymorphism
Code that works with many types, where the compiler chooses which function each call reaches from the types it sees, as with templates and CRTP bases. The call can then be inlined, but objects of different classes share no common type to be stored under.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.