Skip to content
  • Concepts & Constraints (C++20) · C++

Concepts & Constraints (C++20)

LearnViaNotes @learnvianotes· 3 min read

A concept is a named, compile-time predicate on types. Constraining a template with one gives clear error messages and lets you overload on what a type can do.

Using standard concepts

C++
#include <concepts>
#include <iostream>

template <std::integral T>                 // T must be an integer type
T gcd(T a, T b) { return b == 0 ? a : gcd(b, a % b); }

auto half(std::floating_point auto x) { return x / 2; }      // abbreviated syntax

template <typename T>
    requires std::totally_ordered<T>       // requires-clause
const T& clampTo(const T& v, const T& lo, const T& hi) { return v < lo ? lo : hi < v ? hi : v; }

int main() {
    std::cout << gcd(48, 18) << ' ' << half(5.0) << ' ' << clampTo(15, 0, 10) << '\n';
    // gcd(4.5, 1.5);   // error: constraints not satisfied — 'double' is not integral
}
Output
6 2.5 10
C++ — constraint violated
#include <concepts>
template <std::integral T>
T gcd(T a, T b) { return b == 0 ? a : gcd(b, a % b); }
int main() { return static_cast<int>(gcd(4.5, 1.5)); }
Compiler error — short and to the point
error: no matching function for call to 'gcd'
note: candidate template ignored: constraints not satisfied [with T = double]
note: because 'double' does not satisfy 'integral'

Defining a concept

C++
#include <concepts>
#include <iostream>
#include <string>
#include <vector>

template <typename T>
concept Shape = requires(const T& s) {
    { s.area() } -> std::convertible_to<double>;    // must have area() returning something double-like
    { s.name() } -> std::convertible_to<std::string>;
};

struct Square { double side; double area() const { return side * side; } std::string name() const { return "square"; } };
struct Blob   { int size; };

void report(const Shape auto& s) { std::cout << s.name() << ": " << s.area() << '\n'; }

template <typename T>
concept Container = requires(T c) { c.begin(); c.end(); c.size(); };

void info(const Container auto& c) { std::cout << "container of " << c.size() << '\n'; }
void info(const auto&)            { std::cout << "not a container\n"; }   // less constrained fallback

int main() {
    report(Square{3});
    // report(Blob{1});   // error: Blob does not satisfy Shape
    info(std::vector<int>{1, 2});
    info(42);
    std::cout << Shape<Square> << Shape<Blob> << '\n';     // concepts are bool constants
}
Output
square: 9
container of 2
not a container
10
Concept (<concepts>, <iterator>, <ranges>)Satisfied when
std::integral, std::floating_pointBuilt-in integer / floating type
std::same_as<T, U>, std::convertible_to<From, To>Type relationships
std::equality_comparable, std::totally_ordered== / < etc. work
std::copyable, std::movable, std::default_initializableObject capabilities
std::invocable<F, Args...>, std::predicate<F, Args...>Callable with those arguments
std::ranges::range, std::random_access_iteratorRange / iterator categories

Four ways to write a constraint

C++ — equivalent forms
#include <concepts>
template <std::integral T> T a(T x) { return x; }                  // 1. constrained parameter
template <typename T> requires std::integral<T> T b(T x) { return x; }   // 2. requires-clause
template <typename T> T c(T x) requires std::integral<T> { return x; }   // 3. trailing requires
std::integral auto d(std::integral auto x) { return x; }           // 4. abbreviated

What problem do concepts solve compared with plain templates?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report