Appendix B

Concepts

The C++20 feature “concepts” (also known as “named requirements”) allows you to express the template parameter requirements as part of the interface. Before I dive deeper, here is the first example:

template<typename Cont>
    requires Sortable<Cont> // Sortable is a user-defined concept
void sort(Cont& container);
 
template<typename Cont>
void sort(Cont& container) requires Sortable<Cont>; // Trailing 
                                                   // requires clause
 
template<Sortable Cont>    // Constrained template parameters
void sort(Cont& container);

The first version of the generic function sort requires that its argument supports the concept Sortable. The second and the third variants of the function sort are semantically identical. The second version uses the so-called trailing requires clause and just constrained the template parameter to the concept Sortable.

I assume you want to know the following: What are the benefits of concepts?

Essentially, you can use concepts in any template context. Besides the obvious use cases of class templates, function templates, and non-template members of class templates, you can use them for variadic templates. Variadic templates are templates that can accept an arbitrary number of arguments.

template<Arithmetic... Args>
bool all(Args... args) { return (... && args); }       // (1)
 
...
 
std::cout << all(true);                 // true          // (2)
std::cout << all(5, true, 5.5, false);  // false         // (3)

The function template all requires that the arguments are Arithmetic. Arithmetic means they have to be integrals or floating-point numbers. The fold expression (1) applies the logical AND operator to all arguments. In (2) and (3), the function is used. You can also overload on concepts, specialize templates with concepts, or use more than one concept. The following function template requires that the container is a SequenceContainer and that the elements of the container are EqualityComparable.

template <SequenceContainer S,
           EqualityComparable<value_type<S>> T>
Iterator_type<S> find(S&& seq, const T& val) {
    ...
}

With concepts, the usage of type deduction with auto and concepts is unified. auto is just an unconstrained placeholder, and a concept is a constrained placeholder. The rule to keep in mind is simple: Whenever you can use an unconstrained placeholder (auto) with C++11, you can use a constrained placeholder (concept) with C++20.

Integral auto getIntegral(int val) {                                    // (1)
    return val;
}
 
...
 
std::vector<int> vec{1, 2, 3, 4, 5};
for (Integral auto i: vec) std::cout << i << " ";                        // (2)

Integral auto b = true;                                                  // (3)
 
Integral auto integ = getIntegral(10);                                   // (4)

The code snippet shows a few usages of the concept Integral. Instead of the auto keyword, I use Integral auto for the return type of the function getIntegral (1), for the range-based for loop (2), for taking a bool value (3), and for taking an int value (4).

With concepts, C++20 supports a new and very convenient way to define function templates. Using a concept (constrained placeholder) or auto (unconstrained place-holder) in the function signature or as the return type creates a function template.

Integral auto gcd(Integral auto a, Integral auto b) {
    if( b == 0 ) return a;
    else return gcd(b, a % b);
}
 
auto gcd2(auto a, auto b) {
   if( b == 0 ) return a;
   else return gcd(b, a % b);
}

The function template gcd requires that each argument and the return type support the concept Integral. In contrast, the function template gcd2 puts no requirements on its arguments.

Of course, you can define your own concepts. Most of the time it is not necessary to define your concepts because many named requirements are already available with C++20. This holds true for the following two concepts in particular: Integral and Equal. I defined them only for comprehensibility.

template<typename T>
concept Integral = std::is_integral<T>::value;
 
template<typename T>
concept Equal =
requires(T a, T b) {
    { a == b } -> std::convertible_to<bool>;
    { a != b } -> std::convertible_to<bool>;
};

The concept Integral requires that the call std::is_integral<T>::value; return true. std::is_integral is a function from the type-traits library, which evaluates its argument at compile time. The definition of the concept of Equal is more verbose. Both arguments must have the same type T, the type T> has to support the operators == and !=, and both operators have to return a bool.

Detailed information on concepts is available at https://en.cppreference.com/w/cpp/language/constraints. Additionally, you can read my blog post on www.ModernesCpp.com.