template
concept MustBeIncrementable = requires (T x) { x += 1; };
To use this concept in a template, we write:
template
void myfunction(T x)
{
// code goes in here
}
Another way to include the concept into our template is:
template requires MustBeIncrementable
void myfunction(T x)
{
// code goes in here
}
A full working example would be:
#include
#include
template
concept MustBeIncrementable = requires (T x) { x ++; };
template
void myfunction(T x)
{
x += 1;
std::cout << x << '
';
}
int main()
{
myfunction(42); // OK
myfunction(123); // OK
myfunction(345.678); // OK
}
This concept ensures our argument x of type T must be able to accept operator ++, and the argument must be able to be incremented by one. This check is performed during the compile-time. The requirement is indeed true for types char, int, and double. If we used a type for which the requirement is not fulfilled, the compiler would issue a compile-time error.
We can combine multiple concepts. Let us, for example, have a concept that requires the T argument to be an even or an odd number.
template
concept MustBeEvenOrOdd = requires (T x) { x % 2; };
Now our template can include both the MustBeIncrementable and MustBeEvenOrOdd concepts:
template requires MustBeIncrementable && MustBeEvenNumber;
void myfunction(T x)
{
// code goes in here
}
The keyword requires is used both for the expression in the concept and when including the concept into our template class/function.
The complete program, which includes both concept requirements, would be:
#include
#include
template
concept MustBeIncrementable = requires (T x) { x++; };
template
concept MustBeEvenOrOdd = requires (T x) { x % 2; };
template requires MustBeIncrementable && MustBeEvenOrOdd
void myfunction(T x)
{
std::cout << "The value conforms to both conditions: " << x << '
';
}
int main()
{
myfunction(123); // OK
myfunction(124); // OK
myfunction(345); // Error, a floating point number is not even // nor odd
}
In this example, the template will be instantiated if both concept requirements are evaluated to true during compile time. Only the myfunction(123); and myfunction(124); functions can be instantiated and pass the compilation. The arguments of types char and int are indeed incrementable and can be either even or odd. However, the statement myfunction(345); does not pass a compilation. The reason is that the second requirement MustBeEvenOrOdd is not fulfilled as floating-point numbers are neither odd nor even.
Important! Both concepts say: for every x of type T, the statement inside the code-block { } compiles and nothing more. It just compiles. If it compiles, the requirement for that type is fulfilled.
If we want our type T to have a member function, for example, .empty() and we want the result of that function to be convertible to type bool, we write:
template
concept HasMemberFunction requires (T x)
{
{ x.empty() } -> std::convertible_to(bool);
};
There are multiple predefined concepts in the C++20 standard. They check if the type fulfills certain requirements. These predefined concepts are located inside the header. Some of them are: