What is Concept?
A named set of compile-time requirements or constraints that a template
type argument must satisfy. Essentially, it acts as a "gatekeeper" for
templates, ensuring that the data types passed into a template are
capable of performing the operations required inside that template
Concepts is a Predicate means evaluated at compile time
Problem Before Concepts:
Before C++20, if you passed an incompatible data type (like a
std::string) into a template function that performs mathematical
multiplication, the program would still try to compile it. The compiler
would fail deep within the function template, generating thousands of
lines of unreadable error messages.
How Concepts Fix This
With concepts, the compiler checks your data type before trying
to instantiate the template. If the type doesn't fit the requirements,
the compilation stops immediately and gives a clear, highly readable
error message
#include <iostream>
#include <concepts>
// 1. Define the concept
template <typename T>
concept Multipliable = requires(T a) {
a * a; // The type T must support the '*' operator
};
// 2. Apply the concept to a template function
template <Multipliable T>
T doubleValue(T value) {
return value * 2;
}
int main() {
// This works perfectly because integers support multiplication
cout << doubleValue(5); // Outputs: 10
// UNCOMMENTING THE LINE BELOW WILL CAUSE A CLEAN COMPILE ERROR:
// cout << doubleValue("hello");
// Error: constraints not satisfied because 'const char*' is not Multipliable
}
Contraint
This is a compile-time condition that limits the types that can be passed into a template. C++20 gives you three main ways to apply constraints to your code:
1. The requires Clause (Trailing or Ad-hoc)
You can use the requires keyword to specify an explicit constraint after your template declaration.
#include <concepts>
// The constraint here is: std::integral <T>
template <typename T>
requires integral <T>
T modulo(T a, T b) {
return a % b;
}
2. Constrained Template Parameters
Instead of using typename, you can use a concept name directly as the type parameter. This is a shorter, more readable constraint syntax.
#include <concepts>
// 'std::integral' acts as the constraint on type T
template <integral T>
T modulo(T a, T b) {
return a % b;
}