C++ Concepts
Date: 2022-09-20Last modified: 2024-11-02
Table of contents
- C++ 20
- A mechanism to place constraints on your template type parameters.
An alternative to static asserts and type traits
Syntax 1
- the
requires
keyword is used to specify the concept
template <typename T>
requires std::integral<T> // <---
T addA( T a, T b )
{
return a + b;
}
template <typename T>
requires std::is_integral_v<T> // Using a type trait
T addB( T a, T b )
{
return a + b;
}
Syntax 2
template <std::integral T>
T addC( T a, T b )
{
return a + b;
}
Syntax 3
auto addD( std::integral auto a, std::integral auto b )
{
return a + b;
}
Syntax 4
template <typename T>
T addE( T a, T b )
requires std::integral<T>
{
return a + b;
}
Building your own concept
template <typename T>
concept MyIntegral = std::is_integral_v<T>;
template <typename T>
concept Multipliable = requires( T a, T b ) {
a *b; // Just makes sure the syntax is valid
};
template <typename T>
concept Incrementable = requires( T a ) {
a += 1;
++a;
a++;
};
template <typename T>
concept Addable = requires( T a, T b ) {
// noexcept is optional
{
a + b
} noexcept -> std::convertible_to<int>; // Compound requirement
// Checks if a + b is valid syntax, does not throw exceptions(optional),
// and the result is convertible to int(optional)
};
Nested requirement
template <typename T>
concept TinyType = requires( T t ) {
sizeof( T ) <= 4; // Simple requirement : Only checks syntax
// Nested requirement: checks the if the expression is true
requires sizeof( T ) <= 4;
};
Combining requirement
Conjunction &&
template <typename T>
requires std::integral<T> && TinyType<T>
T addF( T a, T b )
{
return a + b;
}
Disjunction ||
template <typename T>
requires std::integral<T> || std::floating_point<T>
T addG( T a, T b )
{
return a + b;
}
Concepts and auto
template <typename T>
void print_number( T n )
{
static_assert( std::is_integral<T>::value, "Must pass in an integral argument" );
std::cout << "n: " << n << std::endl;
}
class A {};
enum E : int {};
template <class T>
T f( T i )
{
static_assert( std::is_integral<T>::value, "Integral required." );
return i;
}
#define SHOW( ... ) std::cout << std::setw( 29 ) << #__VA_ARGS__ << " == " << __VA_ARGS__ << '\n'
int main( [[maybe_unused]] int argc, [[maybe_unused]] char **argv )
{
```cpp
std::cout << std::boolalpha;
SHOW( std::is_integral<A>::value );
SHOW( std::is_integral_v<E> );
SHOW( std::is_integral_v<float> );
SHOW( std::is_integral_v<int> );
SHOW( std::is_integral_v<const int> );
SHOW( std::is_integral_v<bool> );
SHOW( f( 123 ) );
// clang-format off
// std::is_integral<A>::value == false
// std::is_integral_v<E> == false
// std::is_integral_v<float> == false
// std::is_integral_v<int> == true
// std::is_integral_v<const int> == true
// std::is_integral_v<bool> == true
// f( 123 ) == 123
// clang-format on
## Possible output
```txt
std::is_integral<A>::value == false
std::is_integral_v<E> == false
std::is_integral_v<float> == false
std::is_integral_v<int> == true
std::is_integral_v<const int> == true
std::is_integral_v<bool> == true
f( 123 ) == 123