Lesson 8: Concepts and Generic Programming

Lesson 0008 — A Tour of C++, Chapter 8 (§8.1–§8.6)

Chapter 7 introduced templates as "for all T" — but most templates only make sense for types that meet specific requirements: a Vector needs copyable elements, sum() needs a sequence and a number. Chapter 8 is where those requirements become first-class language citizens. Concepts (C++20) are compile-time predicates that name what a template argument must provide — turning unreadable instantiation-time errors into clear messages at the point of use, and enabling concept-based overloading, constrained auto, and disciplined generic programming. The second half of the chapter covers variadic templates (arbitrary numbers of arguments of arbitrary types), fold expressions, argument forwarding, and the template compilation model that explains why "template definitions live in header files."

Why concepts matter now: this is the payoff chapter for everything since §7.2.1. Templates are C++'s main support for generic programming — parametric polymorphism at compile time — and concepts are what make generic code checkable, readable, and overloadable. Interviewers love probing the sharp corners here: where concepts check arguments (point of use) vs. where the template body gets checked (instantiation), what requires requires means, and how concept-based overload resolution picks advance(). This lesson goes deep on all of them.

8.1 Introduction — What Templates Are For

Templates offer three distinct powers:

The first and most common use of templates is to support generic programming: programming focused on the design, implementation, and use of general algorithms — algorithms that accept a wide variety of types as long as they meet the algorithm's requirements on its arguments. Together with concepts, the template is C++'s main support for generic programming; templates provide (compile-time) parametric polymorphism. Alex Stepanov's epigraph for the chapter sets the priority: "Programming: you have to start with interesting algorithms."

8.2 Concepts

Consider the sum() from §7.3.1:

template<typename Seq, typename Value>
Value sum(Seq s, Value v) {
    for (const auto& x : s)
        v += x;
    return v;
}

For this to compile and behave correctly, sum() requires that its first argument be some kind of sequence of elements and its second be some kind of number. More specifically:

We call such requirements concepts. The algorithm is generic in two dimensions: the type of the data structure holding the elements ("the sequence") and the type of the elements themselves.

8.2.1 Use of Concepts

Most template arguments must meet specific requirements for the template to compile — most templates should be constrained templates (§7.2.1). The introducer typename is the least constraining; it requires only that the argument be a type. Usually we can do better:

template<Sequence Seq, Number Num>
Num sum(Seq s, Num v) {
    for (const auto& x : s)
        v += x;
    return v;
}

Once Sequence and Number are defined, the compiler can reject bad calls by looking at sum()'s interface only, rather than its implementation — a huge improvement in error reporting. But this specification is still incomplete: it doesn't say we can add elements of a Sequence to a Number. We can:

template<Sequence Seq, Number Num>
    requires Arithmetic<range_value_t<Seq>, Num>
Num sum(Seq s, Num n);

range_value_t<Seq> is the type of a sequence's elements (§16.4.4); Arithmetic<X,Y> says arithmetic works between X and Y. This saves us from accidentally summing a vector<string> or a vector<int*> while still accepting vector<int> and vector<complex<double>>. The general lesson: when an algorithm requires arguments of differing types, there is usually a relationship between those types that it's good to make explicit.

Two design notes from Stroustrup. Don't constrain too tightly: we needed only +=, but using the general Arithmetic leaves room to later express sum() in terms of + and = instead. Partial specifications are useful: unless a specification is complete, some errors still surface at instantiation time, but even partial concepts express intent and support incremental development.

requires Arithmetic<...> is a requires-clause. The template<Sequence Seq> notation is shorthand for the explicit requires Sequence<Seq>; both notations combine:

template<typename Seq, typename Num>
    requires Sequence<Seq> && Number<Num> && Arithmetic<range_value_t<Seq>, Num>
Num sum(Seq s, Num n);

// equivalently, folding the constraint into the parameter list:
template<Sequence Seq, Arithmetic<range_value_t<Seq>> Num>
Num sum(Seq s, Num n);

In code bases that can't use concepts yet, the fallback is naming conventions and comments — // requires Arithmetic<range_value_t<Sequence>,Number>. Whatever the notation, design templates with semantically meaningful constraints on their arguments (§8.2.4).

8.2.2 Concept-based Overloading

Once templates have proper interfaces, we can overload on their properties, much as we overload functions. A simplified standard advance() that moves an iterator n elements forward has two implementations — one for iterators with only ++, one for iterators with +=:

template<forward_iterator Iter>       // a forward iterator has ++, but not + or +=
void advance(Iter p, int n) {
    while (n--) ++p;
}

template<random_access_iterator Iter>  // a random-access iterator has +=
void advance(Iter p, int n) {
    p += n;
}

The compiler selects the template with the strongest requirements met by the arguments. A list offers only forward iterators; a vector offers random-access iterators:

void user(vector<int>::iterator vip, list<string>::iterator lsp) {
    advance(vip, 10);   // uses the fast advance()  (p += n)
    advance(lsp, 10);   // uses the slow advance()  (while (n--) ++p)
}

Like all overloading this is a compile-time mechanism — no run-time cost — and when the compiler finds no best choice it reports an ambiguity error. The rules are far simpler than general overloading (§1.3). For a single argument among several alternatives:

For an alternative to win overall it must be a match for all of its arguments, at least an equally good match for all arguments as other alternatives, and a better match for at least one argument.

8.2.3 Valid Code

The question of whether template arguments offer what a template requires ultimately boils down to whether some expressions are valid. A requires-expression checks exactly that:

template<forward_iterator Iter>
    requires requires(Iter p, int i) { p[i]; p + i; }   // Iter has subscripting and integer addition
void advance(Iter p, int n) {
    p += n;
}

No, that requires requires is not a typo. The first requires starts the requires-clause; the second starts the requires-expression. A requires-expression is a predicate: true if the statements in its body are valid code, false otherwise.

Stroustrup calls requires-expressions "the assembly code of generic programming": extremely flexible, imposing no discipline, at the bottom of most interesting generic code — and like assembly, they shouldn't appear in ordinary code. They belong inside the implementation of abstractions. If you see requires requires in your code, it's probably too low level and will eventually become a problem. The example above is deliberately hackish — it "forgot" to specify += and the required return types, so some uses pass concept checking yet still fail to compile. Prefer properly named concepts with well-specified semantics, and use requires-expressions primarily in the definitions of those concepts.

8.2.4 Definition of Concepts

Useful concepts (like forward_iterator) come from libraries, including the standard library (§14.5) — using one is easier than writing your own. But simple concepts are not hard to define. A concept is a compile-time predicate specifying how one or more types can be used:

template<typename T>
concept Equality_comparable = requires (T a, T b) {
    { a == b } -> Boolean;   // compare Ts with ==
    { a != b } -> Boolean;   // compare Ts with !=
};

The value of a concept is always bool. The { ... } -> Concept form says "this expression is valid and its result satisfies Concept" — here Boolean, a user-defined concept meaning "usable as a condition" (there is no standard-library boolean concept, so Stroustrup defined one). Testing:

static_assert(Equality_comparable<int>);     // succeeds
struct S { int a; };
static_assert(Equality_comparable<S>);       // fails: structs don't automatically get == and !=

Handling non-homogeneous comparisons is almost as easy — note the default template argument T2 = T, used when the second argument isn't given:

template<typename T, typename T2 = T>
concept Equality_comparable = requires (T a, T2 b) {
    { a == b } -> Boolean;
    { a != b } -> Boolean;
    { b == a } -> Boolean;   // both directions, so int == double and double == int both work
    { b != a } -> Boolean;
};

static_assert(Equality_comparable<int, double>);   // succeeds
static_assert(Equality_comparable<int>);           // succeeds (T2 defaults to int)
static_assert(Equality_comparable<int, string>);   // fails

A Number concept checks arithmetic operations and a zero, making no assumptions about result types — adequate for simple uses:

template<typename T, typename U = T>
concept Number = requires(T x, U y) {
    x+y; x-y; x*y; x/y;    // the four arithmetic operations
    x+=y; x-=y; x*=y; x/=y;
    x = x;                 // copyable
    x = 0;                 // has a zero
};

Number<X> checks X alone; Number<X,Y> checks the two types work together. From that, the symmetric Arithmetic concept used in §8.2.1:

template<typename T, typename U = T>
concept Arithmetic = Number<T, U> && Number<U, T>;   // arithmetic works in both directions

A Sequence concept is more complex — it must provide a value type, an iterator type, begin()/end() returning that iterator, and the iterator must be at least an input_iterator with matching value type:

template<typename S>
concept Sequence = requires (S a) {
    typename range_value_t<S>;                 // S must have a value type
    typename iterator_t<S>;                    // S must have an iterator type
    { a.begin() } -> same_as<iterator_t<S>>;   // begin() returns the iterator type
    { a.end() }   -> same_as<iterator_t<S>>;
    requires input_iterator<iterator_t<S>>;    // the iterator is usable
    requires same_as<range_value_t<S>, iter_value_t<S>>;
};

The typename X; form inside a requires-expression checks that a type exists; requires C; (nested) checks a further constraint. The hardest concepts to define are fundamental language concepts, so prefer an established library's set (§14.5) — the whole Sequence above collapses to concept Sequence = input_range<S>;. For simple cases, an alias hides complexity:

template<class S> using Value_type = typename S::value_type;   // simple, if S has that member

8.2.4.1 Definition Checking

This is the subtle part, and interviewers love it. The concepts specified for a template are used to check arguments at the point of use — they are not used to check the use of the parameters in the template's definition:

template<equality_comparable T>
bool cmp(T a, T b) {
    return a < b;   // the concept guarantees == and !=, but NOT <
}

bool b0 = cmp(cout, cerr);      // error: ostream doesn't support ==  (caught at point of use)
bool b1 = cmp(2, 3);            // OK: int supports == and returns true
bool b2 = cmp(2+3i, 3+4i);      // error: complex<double> supports == but not <
                                //   (caught at instantiation time, when the body is checked)

The concept check accepts the ints and complex<double>s (both have ==) — but int has < so cmp(2,3) compiles, while the < in the body fails when the body is instantiated for complex<double>. Delaying the final check of the template definition until instantiation has two benefits: we can use incomplete concepts during development (gradually improving checking), and we can insert debug/tracing/ telemetry code into a template without changing its interface (interface changes cause massive recompilation). The price: some errors are caught very late in the compilation process, with spectacularly bad messages (§8.5).

8.2.5 Concepts and auto

auto denotes the least constrained concept — it requires only that the value be a value of some type. Taking an auto parameter makes a function into a function template. Concepts strengthen all such uses:

auto twice(Arithmetic auto x) { return x + x; }   // just for numbers
auto thrice(auto x)         { return x + x + x; }  // for anything with a +

auto x1 = twice(7);          // OK: x1 == 14
string s = "Hello ";
auto x2 = twice(s);          // error: a string is not Arithmetic
auto x3 = thrice(s);         // OK: x3 == "Hello Hello Hello "

Concepts can also constrain variable initialization and return types:

auto ch1 = open_channel("foo");        // works with whatever open_channel() returns
Arithmetic auto ch2 = open_channel("foo");  // error: a channel is not Arithmetic
Channel auto ch3 = open_channel("foo");     // OK: assuming Channel is an appropriate concept

Number auto some_function(int x) {
    // ...
    return fct(x);   // an error unless fct(x) returns a Number
}

Constraining a return type catches a type error as close to its origin as possible — better for readability and debugging than the equivalent (verbose, possibly copy-heavy) local variable.

8.2.6 Concepts and Types

A type specifies the set of operations applicable to an object (implicitly and explicitly), relies on function declarations and language rules, and specifies how an object is laid out in memory. A single-argument concept specifies the set of operations too, but relies on use patterns reflecting function declarations and language rules, says nothing about layout, and enables a set of types. Concepts therefore give more flexibility than types, and can express relationships among several arguments. Stroustrup's ideal: eventually most functions will be template functions with arguments constrained by concepts. The notational support isn't perfect — a concept must be used as an adjective, not a noun:

void sort(Sortable auto&);   // OK: 'auto' required
void sort(Sortable&);        // error: 'auto' required after concept name

8.3 Generic Programming

The form of generic programming C++ directly supports: abstract from concrete, efficient algorithms to obtain generic algorithms that combine with different data representations to produce a wide variety of useful software. The abstractions representing fundamental operations and data structures are called concepts [Stepanov, 2009].

8.3.1 Use of Concepts

Good concepts are fundamental and discovered more than designedinteger, floating-point, sequence, and mathematical notions like ring and vector space represent fundamental concepts of a field of application (hence the name). For basic use, the standard concept regular (§14.5) describes types that behave much like an int or a vector: an object of regular type can be default constructed; copied (yielding two independent objects that compare equal); compared with == and !=; and doesn't suffer technical problems from overly clever programming tricks. string is regular — and also totally_ordered (§14.5): comparable with <, <=, >, >=, <=> with the appropriate semantics.

A concept is not just syntactic — it is fundamentally about semantics. Don't define + to divide; that wouldn't match any reasonable number. We don't yet have language support for expressing semantics, so we rely on expert knowledge and common sense — and we should not define semantically meaningless concepts such as Addable and Subtractable. Rely on domain knowledge to define concepts matching fundamental concepts in an application domain.

8.3.2 Abstraction Using Templates

Good abstractions are carefully grown from concrete examples — not invented by preparing for every conceivable need (that direction leads to inelegance and code bloat). Start with one — preferably more — concrete examples from real use and eliminate inessential details:

double sum(const vector<int>& v) {
    double res = 0;
    for (auto x : v)
        res += x;
    return res;
}

What makes this less general than it needs to be? Why just ints? Why just vectors? Why accumulate in a double? Why start at 0? Why add? Answering the first four by making the concrete types template arguments gives the standard library's accumulate (§17.3):

template<forward_iterator Iter, Arithmetic<iter_value_t<Iter>> Val>
Val accumulate(Iter first, Iter last, Val res) {
    for (auto p = first; p != last; ++p)
        res += *p;
    return res;
}

The data structure is abstracted into a pair of iterators representing a sequence; the accumulator's type is a parameter (and must be arithmetic, working with the iterator's value type); the initial value is now an input whose type is the accumulator's type. Code generated for calls with a variety of data structures is identical to hand-coded examples. The process of generalizing from concrete code while preserving performance is called lifting. The best way to develop a template: first write a concrete version, then debug/test/measure it, finally replace concrete types with template arguments. The repeated begin()/end() is tedious, so a range-based overload simplifies common use:

template<forward_range R, Arithmetic<value_type_t<R>> Val>
Val accumulate(const R& r, Val res = 0) {
    for (auto x : r)
        res += x;
    return res;
}

void use(const vector<int>& vec, const list<double>& lst) {
    auto sum  = accumulate(begin(vec), end(vec), 0.0);   // accumulate in a double
    auto sum2 = accumulate(begin(lst), end(lst), sum);
}

Both forms are useful: pair-of-iterators for generality, range for simplicity. For full generality, the += operation itself can be abstracted (§17.3).

8.4 Variadic Templates

A template can accept an arbitrary number of arguments of arbitrary types — a variadic template. A simple function printing values of any type with a << operator:

void user() {
    print("first: ", 1, 2.2, "hello\n"s);              // first: 1 2.2 hello
    print("\nsecond: ", 0.2, 'c', "yuck!"s, 0, 1, 2, '\n');
}

The traditional implementation separates the first argument (the head) from the rest (the tail) and recursively calls itself on the tail:

template<typename T>
concept Printable = requires(T t) { std::cout << t; }   // just one operation!

void print() { }   // what we do for no arguments: nothing

template<Printable T, Printable... Tail>
void print(T head, Tail... tail) {
    cout << head << ' ';   // first, what we do for the head
    print(tail...);        // then, what we do for the tail
}

Printable... indicates that Tail is a sequence of types; Tail... indicates that tail is a sequence of values of those types. A parameter declared with ... is a parameter pack. Each call splits into head + tail; eventually the tail is empty and the no-argument print() handles it. To forbid the zero-argument case (or avoid generating that final call), use a compile-time if:

template<Printable T, Printable... Tail>
void print(T head, Tail... tail) {
    cout << head << ' ';
    if constexpr (sizeof...(tail) > 0)   // compile-time: no print() call generated for the last head
        print(tail...);
}

The strength of variadic templates — accepting any arguments — comes with weaknesses: the recursive implementations can be tricky to get right; the interface's type checking is a possibly elaborate template program, ad hoc rather than defined in the standard; and the recursion can be surprisingly expensive in compile time and compiler memory. They're widely used in the standard library, and occasionally wildly overused.

8.4.1 Fold Expressions

For simple variadic templates, C++ offers a limited form of iteration over a parameter pack — fold expressions:

template<Number... T>
int sum(T... v) {
    return (v + ... + 0);   // add all elements of v starting with 0
}

int x = sum(1, 2, 3, 4, 5);   // x becomes 15
int y = sum('a', 2.4, x);     // y becomes 114 ('a' is 97; 2.4 is truncated)

(v + ... + 0) adds all elements starting from the rightmost: (v[0]+(v[1]+(v[2]+(v[3]+(v[4]+0))))) — a right fold. The operator order swaps for a left fold:

template<Number... T>
int sum2(T... v) {
    return (0 + ... + v);   // (((((0+v[0])+v[1])+v[2])+v[3])+v[4])
}

Folds are a powerful abstraction, closely related to accumulate() — and they need not be numeric. The famous print-with-fold:

template<Printable... T>
void print(T&&... args) {
    (std::cout << ... << args) << '\n';   // chain the << operations left-to-right
}
print("Hello!"s, ' ', "World ", 2017);
// expands to: (((((std::cout << "Hello!"s) << ' ') << "World ") << 2017) << '\n');

Why 2017 in the example? Because folds were added to C++ in C++17 (§19.2.3).

8.4.2 Forwarding Arguments

Passing arguments unchanged through an interface is an important use of variadic templates. A network InputChannel whose transport mechanism is a template parameter — different transports need different constructor arguments:

template<concepts::InputTransport Transport>
class InputChannel {
public:
    // ...
    InputChannel(TransportArgs&&... transportArgs)
        : _transport(std::forward<TransportArgs>(transportArgs)...) {}
    // ...
    Transport _transport;
};

std::forward (§16.6) moves the arguments unchanged from InputChannel's constructor to Transport's constructor — the writer of InputChannel constructs a Transport without knowing what arguments it requires, needing only the common user interface. Forwarding is common in foundational libraries where generality and low run-time overhead are necessary and interfaces are very general.

8.5 Template Compilation Model

At the point of use, template arguments are checked against their concepts — errors found here are reported immediately. What can't be checked there (arguments of unconstrained parameters, uses in the template body) is postponed until code is generated for the template with a particular set of arguments: at template instantiation time. The unfortunate side effects: type errors detected uncomfortably late, and spectacularly bad error messages, because the compiler lacks the type information that hints at the programmer's intent and often detects the problem only after combining information from several places in the program.

Instantiation-time checking provides a compile-time variant of what is often called duck typing — "if it walks like a duck and it quacks like a duck, it's a duck." More technically: we operate on values, and the presence and meaning of an operation depend solely on its operand values, rather than on objects' types determining the operations. What is done at compile time using templates mostly does not involve objects, only values.

The practical consequence: to use an unconstrained template, its definition (not just its declaration) must be in scope at its point of use. With header files and #include, that means template definitions live in header files, not .cpp files — the standard header <vector> holds the definition of vector. Modules (§3.2.2) change this: with modules, source can be organized the same way for ordinary functions and template functions, because a module is semi-compiled into a representation (an easily traversed graph of scope and type information, with a symbol table) that makes importing fast.

8.6 Advice

Here is a summary of the guidance from this chapter. All 18 items, with the section where each is introduced. The C++ Core Guidelines link each item to its recommended practice.

# Guideline §
1 Templates provide a general mechanism for compile-time programming. 8.1
2 When designing a template, carefully consider the concepts (requirements) assumed for its template arguments. 8.3.2
3 When designing a template, use a concrete version for initial implementation, debugging, and measurement. 8.3.2
4 Use concepts as a design tool. 8.2.1
5 Specify concepts for all template arguments. 8.2
6 Whenever possible use named concepts (e.g., standard-library concepts). 8.2.4, 14.5
7 Use a lambda if you need a simple function object in one place only. 7.3.2
8 Use templates to express containers and ranges. 8.3.2
9 Avoid "concepts" without meaningful semantics. 8.2
10 Require a complete set of operations for a concept. 8.2
11 Use named concepts. 8.2.3
12 Avoid requires requires. 8.2.3
13 auto is the least constrained concept. 8.2.5
14 Use variadic templates when you need a function that takes a variable number of arguments of a variety of types. 8.4
15 Templates offer compile-time "duck typing." 8.5
16 When using header files, #include template definitions (not just declarations) in every translation unit that uses them. 8.5
17 To use a template, make sure its definition (not just its declaration) is in scope. 8.5
18 Unconstrained templates offer compile-time "duck typing." 8.5

Retrieval Quiz

Where concepts check arguments

With template<Sequence Seq, Number Num> Num sum(Seq s, Num v); — when does the compiler reject a call with a non-sequence or non-number argument?

Definition checking

template<equality_comparable T> bool cmp(T a, T b) { return a < b; } — which call fails, and where?

requires requires

What does the double requires in requires requires(Iter p, int i) { p[i]; p+i; } mean?

Concept-based overloading

Both advance() overloads are viable for a vector<int>::iterator. Which is chosen, and why?

Concept definition basics

What does { a == b } -> Boolean; inside a requires-expression require?

Default template arguments in concepts

In template<typename T, typename T2 = T> concept Equality_comparable = ... — what does Equality_comparable<int> check?

Symmetric concepts

Why is Arithmetic defined as Number<T, U> && Number<U, T> rather than just Number<T, U>?

Concepts and auto

Which statement about Arithmetic auto x = open_channel("foo"); is true?

Concept as adjective

Why does void sort(Sortable&); fail to compile while void sort(Sortable auto&); works?

Variadic templates and folds

How does template<Number... T> int sum(T... v) { return (v + ... + 0); } evaluate sum(1, 2, 3)?

Compilation model

Why do template definitions (e.g., vector's) live in header files rather than .cpp files?


Notes

Concepts :-

concept compile-time predicate on types what a template argument must provide

checking at point of use, interface only clear early errors

3 template powers types/values/templates as args ; context weaving ; compile-time computation

generic programming algorithms over types meeting requirements parametric polymorphism

Constraints :-

template<Sequence Seq> shorthand for requires Sequence<Seq>

requires-clause explicit constraints (eg Arithmetic<range_value_t<Seq>,Num>)

typename least constraining ; only ''is a type''

don't over-constrain general concepts beat narrow requirements (eg +=)

partial specs express intent ; some errors still at instantiation

Concept overloading :-

advance() forward_iterator (++ only) v/s random_access_iterator (+=)

selection strongest requirements met by args wins compile-time, no cost

rules non-match excluded | single match chosen | stricter chosen | equal ambiguity

requires-expression :-

requires requires(...) clause + expression ; predicate ''is this code valid?''

use in concept definitions ; assembly of generic programming

bare requires requires in code too low level ; prefer named concepts

Concept definitions :-

template<typename T> concept X = requires(T a) { {a==b} -> Boolean; }

{expr} -> C expr valid AND result satisfies C ; concept value always bool

default template arg typename T2 = T

Arithmetic Number<T,U> && Number<U,T> symmetric

Sequence value type + iterator type + begin/end + input_iterator ; or input_range<S>

Definition checking :-

concepts check arguments at point of use

template body checked at instantiation time

cmp(2+3i,3+4i) passes == concept, fails < in body late error

benefits incomplete concepts OK ; debug code without interface changes

Concepts and auto :-

auto least constrained concept ''some type''

Arithmetic auto x = ... constrains initialization counters auto overuse

constrained return Number auto f() error near origin

concept = adjective Sortable auto& ; Sortable& error

Concept v/s type :-

type operations + layout + one type

concept operations + semantics, no layout set of types

semantics matter × Addable/Subtractable meaningless

regular default-constructible + copyable + == ; behaves like int/vector

Lifting :-

lifting concrete generic while preserving performance

recipe concrete version debug/test/measure parameterize types

accumulate pair-of-iterators (general) v/s range (simple)

5 questions why ints? vector? double? start 0? add? template args

Variadic templates :-

parameter pack Tail... ; head + tail recursion

termination empty print() | if constexpr(sizeof...(tail) > 0)

fold right (v + ... + 0) v[0]+(v[1]+(...+0))

left (0 + ... + v) ((0+v[0])+v[1])+... ; folds = C++17

std::forward pass args unchanged through interface

cost compile time + memory ; tricky recursion

Compilation model :-

definition must be in scope at use templates in headers, not .cpp

duck typing operations depend on values, not declared types

late checking atrocious error messages

modules fix it semi-compiled, fast to import


Primary source: Stroustrup, B. (2022). A Tour of C++, 3rd ed., Chapter 8: "Concepts and Generic Programming." Addison-Wesley.
Reference: Chapter 1–8 Quick Reference & Glossary — keep it beside you while you study.
Recommended supplement: cppreference on constraints and concepts, requires-expression, fold expressions, parameter packs, and std::forward.

Questions? Ask your agent — your teacher — about anything unclear: how definition checking delays errors to instantiation time, when to write your own concept instead of using the standard library's, or how std::forward preserves argument categories. Follow-ups are expected, not optional.

Chapters

  1. Lesson 1: The Basics (Ch. 1)
  2. Lesson 2: User-Defined Types (Ch. 2)
  3. Lesson 3: Separate Compilation (Ch. 3)
  4. Lesson 4: Error Handling (Ch. 4)
  5. Lesson 5: Classes (Ch. 5)
  6. Lesson 6: Essential Operations (Ch. 6)
  7. Lesson 7: Templates (Ch. 7)
  8. Lesson 8: Concepts and Generic Programming (Ch. 8 — this lesson)
  9. Lesson 9: Library Overview (Ch. 9)
  10. Lesson 10: Strings and Regular Expressions (Ch. 10)
  11. Lesson 11: Input and Output (Ch. 11)
  12. Lesson 12: Containers (Ch. 12)
  13. Lesson 13: Algorithms (Ch. 13)
  14. Lesson 14: Ranges (Ch. 14)
  15. Lesson 15: Pointers and Containers (Ch. 15)
  16. Lesson 16: Utilities (Ch. 16)
  17. Lesson 17: Numerics (Ch. 17)
  18. Lesson 18: Concurrency (Ch. 18)
  19. Lesson 19: History and Compatibility (Ch. 19)
← Course Dashboard ← Ch. 7: Templates Ch. 9: Library Overview → Quick Reference →