Skip to content

Beginning of the lesson

Concepts (C++20)

Constraining templates readably - named, checkable requirements on template parameters

Big Idea: A concept is a named, compile-time predicate on one or more template parameters. Instead of writing a template that silently accepts any type and then explodes with a wall of errors deep inside its body, you attach a concept to say up front exactly what the type must be able to do. If the type does not satisfy the concept, the compiler rejects the call at the call site with a short, readable message - and the constrained overload simply drops out of consideration. Concepts turn template requirements from tribal knowledge in comments into first-class, enforced language contracts.
By the end you will be able to:
  • explain why unconstrained templates and old enable_if/SFINAE tricks produce terrible errors,
  • apply the standard-library concepts (std::integral, std::convertible_to, std::sortable, ...),
  • attach constraints four different ways, including the abbreviated auto form,
  • write your own concept with a requires-expression (simple, compound, type and nested requirements),
  • use constraint subsumption to pick the most specialised overload.

1. The problem: unconstrained templates and SFINAE

You already know how to write a function template. The trouble is that a bare template<typename T> promises nothing. The type requirements live only inside the body, so a wrong type compiles until the moment some inner operation fails:

template <typename T>
T twice(T x) { return x + x; }   // silently assumes T has operator+

twice(std::string{"hi"});        // compiles: "hihi"
twice(std::vector<int>{1,2});     // ERROR - but deep inside <vector>, pages of noise

Before C++20 the fix was SFINAE ("Substitution Failure Is Not An Error") with std::enable_if: you smuggled a constraint into the signature so a bad type made the overload disappear. It worked, but it was write-only:

template <typename T,
          typename = std::enable_if_t<std::is_integral_v<T>>>
T twice(T x) { return x + x; }   // "only if T is integral" - but who can read that?

The intent ("T must be integral") is buried in template plumbing, the error messages are still awful, and the constraint cannot be reused or named. Concepts replace all of this with a readable, named contract.

Continue this lesson on YourAIZone

The full lesson has the complete explanation, worked examples, common mistakes, tests with instant checking and an AI tutor. Start right away as a guest, or create an account to keep your progress.

In the full lesson

  • The complete explanation
  • Worked examples
  • Common mistakes and how to avoid them
  • Test questions: 15
  • AI tutor that answers questions about the topic
  • Progress tracking for students, parents and teachers