Article

Demystifying C++20 Requires-Clauses

December 1, 2019

C++20 introduces requires-clauses for constraining templates. They are already usable in current trunk versions of GCC and Clang on Compiler Explorer. Below is the minimal form:

template<typename T>
  requires is_standard_layout_v<T> && is_trivial_v<T>
void fun(T v);

fun(1);        // OK
fun(std::string{}); // error

Two-step check

First the compiler verifies that every operand is well-formed. If any expression is ill-formed the whole constraint is considered not satisfiedrather than a hard error.

template<typename T>
  requires is_trivial_v<typename T::value_type>
void g(T x);          // enabled only if T::value_type exists

g(1);                 // falls back to other overloads

Parentheses matter

template<typename T>
  requires (!is_trivial_v<T>)    // OK
void f1(T);

template<typename T>
  requires !is_trivial_v<T>      // ill-formed
void f2(T);

Conjunction & disjunction

template<typename T, typename U>
  requires std::is_trivial_v<typename T::value_type>
        || std::is_trivial_v<typename U::value_type>
void h(T, U);

Sub-constraints are evaluated left-to-right and stop once the result is known.

Variadic constraints

template<typename T>
concept TrivVT = std::is_trivial_v<typename T::value_type>;

template<typename... Ts>
  requires (TrivVT<Ts> || ...)
void pack_fun(Ts...);

Overload selection

template<typename T>
void ovl(T) { std::cout << "generic"; }

template<typename T>
  requires std::is_integral_v<T>
void ovl(T) { std::cout << "integral"; }

ovl(1);     // integral
ovl("x");   // generic

Key points

  • Constraint operands must be well-formed; failure disables the template.
  • Wrap complex predicates in parentheses to avoid parsing issues.
  • || and && inside requires-clauses denote constraint disjunction/conjunction with lazy evaluation.
  • Using concepts enables automatic ordering of more-constrained overloads.