## Introduction

A *C++ 20* concept is a named predicate (true/false expression) that constrains templates. It improves the readability of code and facilitates finding bugs. Concepts can also be used for function overloading, template specialization, and creating meta-functions. Concepts along with `if-constexpr`

are modish alternatives for unpleasant SFINAE pattern.

## Prerequisites

This post assumes you are familiar with templates and auto keyword. The codes here are compiled with GCC v10.2.

## Why Concepts?

Concepts make a template code easy to read and help to find bugs.

In the example below, it is not clear what `T`

is unless you read the whole function. The function works well with arrays. But let’s, instead of an array, pass a wrong object to the function:

```
#include<iostream>
template<class T>
double sum(T& items){
double sum=0;
for (auto& item:items)
sum+=item;
return sum;
}
class A{};
int main(){
A a;
auto c = sum(a);
}
```

Look at the errors, it takes a bit of time to decipher:

```
<source>: In instantiation of 'double sum(T) [with T = A]':
<source>:15:19: required from here
<source>:6:5: error: 'begin' was not declared in this scope; did you mean 'std::begin'?
6 | for (auto& item:items)
| ^~~
| std::begin
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/string:54,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/locale_classes.h:40,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/ios_base.h:41,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ios:42,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ostream:38,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/iostream:39,
from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/range_access.h:108:37: note: 'std::begin' declared here
108 | template<typename _Tp> const _Tp* begin(const valarray<_Tp>&);
| ^~~~~
<source>:6:5: error: 'end' was not declared in this scope; did you mean 'std::end'?
6 | for (auto& item:items)
| ^~~
| std::end
In file included from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/string:54,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/locale_classes.h:40,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/ios_base.h:41,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ios:42,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/ostream:38,
from /opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/iostream:39,
from <source>:1:
/opt/compiler-explorer/gcc-10.2.0/include/c++/10.2.0/bits/range_access.h:110:37: note: 'std::end' declared here
110 | template<typename _Tp> const _Tp* end(const valarray<_Tp>&);
| ^~~
Compiler returned: 1
```

The errors become even more entangled when something goes wrong in nested class templates.

## Definition

Here, `Array`

concept is defined to tell the user of this code that `T`

should be an array:

```
#include<iostream>
template<class U>
concept Array = std::is_array<U>::value;
template<Array T> // Apply Array concept
double sum(T& items){
double sum=0;
for (auto& item:items)
sum+=item;
return sum;
}
```

There are many useful structs like `is_array`

in `type_traits`

header which can be used for defining a concept.

Again we injected the wrong parameter into the `sum`

function:

```
class A{};
int main(){
A a;
int b[2]={1,2}; // b is the right input for sum()
std::cout<<sum(a);
}
```

Now let’s check the errors, they directly point out that `a`

is not an array:

```
: In function 'int main()':
:19:21: error: use of function 'double sum(T&) [with T = A]' with unsatisfied constraints
19 | std::cout<<sum(a);
| ^
:7:8: note: declared here
7 | double sum(T& items){
| ^~~
:7:8: note: constraints not satisfied
: In instantiation of 'double sum(T&) [with T = A]':
:19:21: required from here
:4:9: required for the satisfaction of 'Array<T>' [with T = A]
:4:35: note: the expression 'std::is_array< <template-parameter-1-1> >::value [with <template-parameter-1-1> = A]' evaluated to 'false'
4 | concept Array = std::is_array<U>::value;
| ^~~~~
```

## Concept for auto keyword

`auto`

keyword is used for setting the type of a variable based on its initializer. For more details, see my post on `auto`

here. For example

```
auto i=1; // i is int
```

With the aid of concepts, we can constrain `auto`

, for instance this compiles well:

```
#include<concepts>
std::integral auto i=2;
```

but if you change the last line to

```
std::integral auto i=2.2;
```

We get an error because `2.2`

is a double, not an integral number. Therefore, the function below accepts any integral type like `int`

, `long`

, `long long`

and so on:

```
void f(integral auto i){
/* some code*/
}
```

But gives an error if non-integral types are passed.

## Concepts and constraints

Two constraints can create conjunction with `&&`

operator where both must be true

```
template<class T>
concept ConstInt = std::is_const<T>::value && std::is_integral<T>::value;
template<ConstInt T>
void f(T a){};
```

Note that If `is_const<T>::value`

is false, `is_integral`

term is ignored.

Disjunction can be created with `||`

operator where either constraint can be true

```
template<class T>
concept Arithmetic = std::is_integral<T>::value || std::is_floating_point<T>::value;
```

Constraints can be created with other concepts:

```
template<class T>
concept ConstArithmetic = Arithmetic && is_const<T>::value;
```

Before defining your concepts, it is worth checking already defined concepts in the `<concepts>`

header.

## Requires clause

Instead of imposing a concept inside the template brackets:

```
template<Arithmetic T>
void f(T a){/*function definition*/};
```

we can enforce it just after template declaration using `requires`

:

```
template<class T>
requires Arithmetic<T>
void f(T a)
{/*function definition*/};
```

or after the function declaration

```
#include<concepts>
template<class T>
void f(T a) requires integral<T> // integral is in header <concepts>
{/*function definition*/};
```

## Requires expressions

If working with type traits is hard, we can explain what we expect from a type with an object of that type:

```
template<class T>
concept isVector = requires (T a) // a is an object of type T
{
a.begin(); // constraint #1: a.begin() must be meaningful.
a.reserve(1); // constraint #2: a.reserve(1) must work.
a.data(); // constraint #3: a.data() is there.
};
template<isVector T>
class Box{};
```

So users get errors if a `Box`

object is created that violates constraints #1, #2 and #3.

Constraints can be imposed on types too:

```
template<class T>
concept MyArray = requires (T a){
a[0]; // Accessing elements through [] works.
a.size(); // a must have size() method.
typename T::Type; // T must contain a datatype, Type.
};
template<MyArray T>
void DisplayArray(T x){
typename x::Type sample;
std::cout<<sample;
for (size_t i=0;i<x.size();i++)
std::cout<<x[i]<<" ";
}
```

## Compounds

The `requires`

expression can constrain the return type of its statements

```
template<class T>
concept AddableInt = requires (T a){
{ a + a } -> std::same_as<int>;
}
```

The above concept imposes two constraints:

`a+a`

must be valid.`decltype(a+a)`

must be the same as`int`

.

Concept `same_as`

is defined in the `<concepts>`

header.

We can also have multiple expressions:

```
template<class T>
concept MyPointer = requires (T a){
{ *a + 1} -> std::convertible_to<MyBaseClass>;
{ a->sum()} -> std::same_as<double>;
};
```

## Concepts and Constepxr

Concepts can be employed in `constexpr`

assessments:

```
template<typename T>
concept hasSize = requires (T a){ a.size();};
auto f(auto x){
if constexpr (hasSize<decltype(x)>){
return x.size();
else
return x;
}
int main(){
std::vector<int> vec{1,2};
int i=5;
std::cout<<f(i); // 5
std::cout<<f(vec);// 2
}
```

To read more on `constexpr`

, see my post here.

## Partial ordering

Imaginge constraint A is looser than constraint B and A contains B. If a template type matches both, the template that constrained by B is selected. This is called partial ordering of constraints. For example:

```
// strict constraint
template<class T>
concept Integral = std::is_integral_v<T>;
// loose constraint
template<class T>
concept Arithmetic = Integral<T> ||
std::is_floating_point_v<T>;
auto f(Integral auto x){
std::cout<<"Integral.";
}
auto f(Arithmetic auto x){
std::cout<<"Arithmetic.";
}
f(2.2); // Arithmetic
f(1); // Integral
```

The idea is very similiar to template specialization technique that is used to select a type based on some criteria.

## Example

Create a meta-function using partial ordering of concepts that creates different outputs for STL containers, std::array, std::vector and everything else.

Let’s solve it via two steps: firstly, defining or finding some suitable concepts and, secondly, apply them to meta-functions.

### Step 1: create constraints

There are already many type traits in `<type_traits>`

header and concepts in `<concepts>`

header, check them before re-inventing the wheel.

In the below example, I start from a general STL container and filter types until I reach `std::array`

and `std::vector`

.

A STL container must have `begin()`

:

```
template<typename T>
concept isContainer = requires (T a){ a.begin();};
```

Arrays and vectors have `data()`

function:

```
template<typename T>
concept hasData = requires (T a){ a.data();};
template<typename T>
concept isArrayVector = isContainer<T> && hasData<T>;
```

In camparison to arrays, vectors have `reserve()`

function:

```
template<typename T>
concept hasReserve = requires (T a){ a.reserve(1);};
template<typename T>
concept isVector = isArrayVector<T> && hasReserve<T>;
```

So arrays do not have `reserve()`

function:

```
template<typename T>
concept isArray = isArrayVector<T> && !hasReserve<T>;
```

### Step 2: Define meta-function

A meta-function is a struct whose arguments are template parameters. It is overloaded similar to the run-time function.

Let’s create a meta-function that stores the name of the types. The first instance **must** be a generic or default behaviour which happens when none of the special cases is satisfied:

```
template<typename T>
struct Info{
static constexpr char TypeName[] = "Unknown";
};
```

Now we define the special ones using our concepts:

```
template<isArray T>
struct Info<T>{
static constexpr char TypeName[] = "Array";
};
template<isContainer T>
struct Info<T>{
static constexpr char TypeName[] = "Container";
};
template<isVector T>
struct Info<T>{
static constexpr char TypeName[] = "Vector";
};
```

The compiler tries to match a type to the concept with most narrowed down conditions, so the priorities for matching are

**1- Array or Vector**

**2- Container**

**3- Generic case**

Note that I purposefully mashed the order that special cases of `info`

are written to show that the order is not important as long as concepts are narrowed down nicely.

We can see the outcome like:

```
int main(){
std::vector<double> vec;
std::array<int,2> arr;
std::list<int> lis;
int s;
std::cout<<Info<decltype(lis)>::TypeName; // Container
std::cout<<Info<decltype(vec)>::TypeName; // Vector
std::cout<<Info<decltype(arr)>::TypeName; // Array
std::cout<<Info<decltype(s)>::TypeName; // Unknown
}
```

This example can also be written using `if constexpr`

and `constexpr`

functions. Can you give it a go?

## Read more

If you are interested in how to use `constexpr`

variables, conditions, and functions see this post.

Or if you just want to brush up your C++ template skills, see this post.

## Latest Posts

- A C++ MPI code for 2D unstructured halo exchange
- Essential bash customizations: prompt, ls, aliases, and history date
- Script to copy a directory path in memory in Bash terminal
- What is the difference between .bashrc, .bash_profile, and .profile?
- A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks