A requires-expression is used to express constraints on template arguments. A basic building block of these constraints is the capability
to generate a subexpression whose type depends on a template argument.
The traditional way to write such a subexpression is by using std::declval<>
(doing something more naive such as
T{}
is not as generic, for instance, it requires T to be default-constructible). This is, however, very verbose and can be error prone:
declval<T>()
yields an expression of type T&&
, while referencing a variable directly produces an lvalue
(T&
). This, in many cases, leads to concepts incorrectly requiring only move-construction, while copies are made by the
implementation.
Require-expressions introduce a more natural way to achieve that. When writing a requires-expression, it is possible to add a
parameter list, similar to function parameters, and these parameters can be used later in the expression. This syntax is less verbose, more
expressive, and less error-prone and should be preferred over calling std::declval
in requires-expressions.
Noncompliant code example
template<typename T>
concept C1 = requires {
std::declval<T const&>() + // Noncompliant
std::declval<T const&>(); // Noncompliant
};
template<typename T>
concept C2 = requires {
std::declval<T const&>() + // Noncompliant
std::declval<typename T::type const&>(); // Noncompliant
};
Compliant solution
template<typename T>
concept C1 = requires (T const &t) {
t + t;
};
// Note that if T::type is not a valid expression, no syntax error is
// triggered, the concept will simply not be satisfied
template<typename T>
concept C2 = requires (T const t, typename T::type const u) {
t + u;
};