There we gooo!

# Intent

Centralize **static assertions**, making them **reusable** to **avoid code duplication**.

# Motivation

Let’s check a simple implementation of a calculator! who doesn’t love them?

We want to make it work with **only signed numbers of 32bits (or less)**. If one of those constraints isn’t met, we report a **compilation error** with the appropriate message.

```
namespace calculator
{
template<typename T>
auto add(T left, T right)
{
static_assert(is_integral_v<T>, "not an integral type");
static_assert(is_signed_v<T>, "only signed types allowed");
static_assert(sizeof(T) <= sizeof(int32_t), "too big");
return left + right;
}
template<typename T>
auto subtract(T left, T right)
{
static_assert(is_integral_v<T>, "not an integral type");
static_assert(is_signed_v<T>, "only signed types allowed");
static_assert(sizeof(T) <= sizeof(int32_t), "too big");
return left - right;
}
// - multiply
// - divide
// ...
}
```

I hope I’m not the only one that feels uneasy about this code.

While it *is* technically correct, those **copy-pasted blocks** of `static_assert`

all over around don’t feel like the best approach to solve this problem. I wouldn’t like to be the guy maintaining those assertions in the future…

unscalable code == unmaintainable code

# The pattern

Since we are **always** applying the same assertions, let’s extract them to a common place. I like to call those **constraint classes**.

## Constraint

A constraint is a

forcedcondition (usually a type trait) over a type.

It isalwayschecked incompilation time(zero runtime overhead).## Constrained Type

A metafunction that checks constraints over a type and gets enabled when those are valid.

Very similar to

`enable_if<_, T>`

but using static assertions for getting readable error messages.## Constraint Class

Templated class that defines all the constraints by using

type traitsandstatic_assert.

### Creating the constraint class

We will rely on the template parameter `T`

to perform the assertions. Every time this class will be **resolved** using a type `T`

that doesn’t satisfy the constraints, it will throw a **readable compilation error**.

```
template<typename T>
struct number_constraints
{
using type = T;
static_assert(is_integral_v<T>, "not an integral type");
static_assert(is_signed_v<T>, "only signed types allowed");
static_assert(sizeof(T) <= sizeof(int32_t), "too big");
};
```

### Creating the constrained type

Type aliases are great for defining those. This solution allows **complete type compatibility** (in the end it resolves to `T`

) and **zero overhead**.

```
template<
typename T,
typename = typename number_constraints<T>::type> // runs the check
using number = T;
```

Every time our code uses a constrained type we gain multiple things:

Expressing intent: Hey, this parameter is a number!Improving readability: naming the bunch of assertions makes the code easier to follow.Abstracting the implementation: just use it and it works, no internal knowledge required.

And all of this **without losing any other advantage**! really great, isn’t it?

# Fixing our calculator

Hands on keyboard, we have some shitty-code to fix!

```
namespace calculator
{
template<typename T>
auto add(number<T> left, number<T> right)
{
return left + right;
}
template<typename T>
auto subtract(number<T> left, number<T> right)
{
return left - right;
}
// ...
}
```

Every time we pass a parameter, the compiler will try to match the type with our constrained one. Such a beautiful function signature we got, our code maintainability basically resurrected.

Enjoy the satisfaction that comes from doing little things well. |

### Constraints in action

A simple test shows that the constraints are working properly.

```
sum<int8_t>(1, 5); // ok
sum<int16_t>(1, 5); // ok
sum<float>(1, 5); // error: not an integral type
sum<uint16_t>(1, 5); // error: only signed types allowed
sum<int64_t>(1, 5); // error: too big
```

Compilation **errors** are also **super easy to read** (no huge and shitty template traces).

```
In instantiation of 'struct number_constraints<int64_t>':
required by substitution of
'template<class T> auto sum(number<T>, number<T>) [with T = int64_t]'
error: static assertion failed: too big
static_assert(sizeof(T) <= sizeof(int32_t), "too big");
~~~~~~~~~~^~~~~~~~~~~~~~~~~~
Compiler returned: 1
```

# When to use it

This pattern covers a simple case: **reusing static_assert**.

## This pattern will suit you if:

- Your codebase contains
duplicated`static_assert`

with thesame intent.- You want to
improve readabilityon those assertions (basically give them a name).## This pattern won’t help you if:

- You are using
SFINAE/overloadsfor safe-fallbacks instead of`static_assert`

.

SFINAE is a great hack, we could transform those assertions to a type trait, **but it has some drawbacks**:

- You will have
**one generic error**(`T is not a number`

) instead of all the detailed ones. - Traits are more
**unreadable/difficult to maintain**. **Huge errors**with template traces when using`enable_if_t`

.

There are some *possible* tricks that allow constraint-classes behave like a trait, but they require *a lot* of metaprogramming and they’re **too long/complex for this article**.

I leave that as an exercise for the reader (or a future article).

The magic trick

defining a constexpr overloaded templated lambda that acts like a type trait and fallbacks to a

`static_assert`

🙂 (example)

# Sample code

All the code for this article is available here.

If you want to get informed about new content, check the

SubscribeandAtom feedbuttons in the top menu.Thanks for reading!