Concepts and Template Constraints
Templates and Generic Programming

5.4 Concepts and Template Constraints

Historically, C++ templates were completely unconstrained. If you wrote a function that added two variables using the + operator and a developer passed a type that did not support addition, the compiler would attempt to build the function body anyway, resulting in massive compiler errors that were incredibly difficult to parse.

C++20 solved this problem by introducing Concepts, allowing you to explicitly constrain template parameters.

Abstract Syntax Tree (AST) Simplification

With the old SFINAE hack, the compiler had to construct a massive Abstract Syntax Tree (AST) in memory for every possible template, try to substitute types, wait for it to fail, and then discard the AST. This caused compile times to explode.

Concepts let the compiler reject invalid template arguments earlier in overload resolution, often producing shorter diagnostics than deep instantiation failures. They do not skip parsing or AST construction entirely, rather, failed concept checks remove candidates before the compiler instantiates unusable bodies.

The Nightmare of Unconstrained Errors

If a template compilation fails deep inside a nested function, the compiler prints every single attempted template substitution path. This creates a wall of error text. Concepts fix this by validating types before compiling the function body.

C++20 Concepts and the `requires` Clause

Concepts define constraints that types must satisfy. The standard library provides built in concepts like std::integral, std::floating_point, and std::copyable:

#include <concepts>
#include <iostream>

// This function only accepts types that satisfy the std::integral concept!
template <typename T>
requires std::integral<T>
T addIntegers(T a, T b) {
    return a + b;
}

// Cleaner shorthand syntax using constrained auto parameters
void printIntegral(std::integral auto x) {
    std::cout << "Integer: " << x << '\n';
}
Constraining templates with integral concepts.

Creating Custom Concepts

You can define your own concepts using the concept keyword and requires expressions to check for method signatures or operators:

#include <concepts>

// Define a custom concept that checks if a type can be incremented
template <typename T>
concept Incrementable = requires(T x) {
    { ++x } -> std::same_as<T&>;
};

template <typename T>
requires Incrementable<T>
void incrementValue(T& val) {
    ++val;
}
Creating a custom concept constraint.
  • The Security Guard Metaphor: Think of C++20 Concepts like hiring a security guard at the door of your function. In old C++ templates, anyone could enter. The compiler only noticed they did not fit when they started breaking chairs inside (attempting to use operators they did not support), forcing a chaotic cleanup. Concepts stop visitors at the door, check their type credentials, and reject them instantly if they do not match, producing a clean error message.
Finished reading this lesson?