A concept is a set of restrictions on template parameters evaluated at compile time. In this shot, we will write a concept in a function using the requires
clause.
requires
clauseWe use the requires
clause between the template parameter list and the function return type, which is auto
in this case.
#include <iostream>#include <concepts>template <typename T>concept Number = std::integral<T> || std::floating_point<T>;template <typename T>requires Number<T>auto add(T a, T b) {return a+b;}int main() {std::cout<<add(2,1.2f)<<'\n';return 0;}
Note: We use the concept and define our constraint in the
requires
clause, expecting that anyT
template parameter must satisfy the conceptNumber
requirements.
If we want the capability of adding up numbers of multiple types, we’d need to introduce a second template parameter.
template <typename T,
typename U>
requires Number<T> && Number<U>
auto add(T a, U b) {
return a+b;
}
Then calls such as add(1, 2.14)
will also work. Please note that the concept
was modified. The drawback is that you’d need to introduce a new template parameter for each new function parameter and its requirement.
It gives us some error because both parameters of the add
function should be the same type.
If we want to add two numbers of multiple types, we have to change our template. Our template should look like this now:
#include <iostream>#include <concepts>template <typename T>concept Number = std::integral<T> || std::floating_point<T>;template <typename T, typename U>requires Number<T> && Number<U>auto add(T a, U b) {return a+b;}int main() {std::cout<<add(5,42.1f)<<'\n';return 0;}
We can also express more complex constraints with the requires
clause. For the sake of an example, let’s just inline the definition of Number
:
template <typename T>requires std::integral<T> || std::floating_point<T>auto add(T a, T b) {return a+b;}
For better readability, in most cases, it’s a good idea to name your concepts, especially if you have more complex representations. Take a look at the code below for a full example.
#include <iostream>#include <concepts>template <typename T>concept Number = std::integral<T> || std::floating_point<T>;template <typename T, typename U>requires Number<T> && Number<U>auto add(T a, U b) {return a+b;}template <typename T, typename U>requires std::integral<T> || std::floating_point<T> &&\std::integral<U> || std::floating_point<U>auto add2(T a, U b) {return a+b;}int main() {std::cout<<add(5,42.1f)<<'\n';std::cout<<add2(42.1f,5)<<'\n';return 0;}
The add()
on line 8 is consuming a named concept, Number
, using the requires
clause. It takes two numbers as a parameter, which should be either integer
or floating_point
, and returns the sum of both numbers.
On line 16, another function, add2()
, is defined, which takes two numbers as parameters and returns the sum, but uses an unnamed concept through the requires
clause.