Forbid a particular specialization of a template
Tags: cpp, programming, todayilearned, todayisearched
Published: 2021-01-11 08:47:05

Let's imagine a simple template function that performs basic numerical computations:

```cpp
template <typename T>
T f(T t) {
    return 2 * t + 3;
}
```

Nothing seems wrong with this function. You can use it on several types: `f(15)` returns 33 and `T` is `int`, `f(2.3)` returns 7.6 and `T` is `double`, etc. If `T` is not a type that supports addition and multiplication, you get a compiler error. Example with `f("hello")`:

>`error: invalid operands to binary expression ('int' and 'const char *')`

Nevertheless, you can run into cases that you had not planned. For instance, `f(true)` is valid, doesn't raise any warning, and returns `true` (in fact, it returns 5 converted to a Boolean, which is `true`).

Let's try how we can reject `f<bool>` at compile-time. We will see that the possibilities to forbid a particular specialization of a template have evolved with the versions of C++. For each technique, I will show the error my compiler, [clang](https://clang.llvm.org/), generates for `f(true)`.

_Note that my purpose here is not to properly handle types that don't support addition and multiplication, just to forbid a particular specialization._

## =delete on specialization

**Since C++11**

The first solution is to explicitly delete the specialization of `f()` for `T == bool`:

```cpp
template<>
bool f(bool) = delete;
```

>`error: call to deleted function 'f'`
>
>`note: candidate function [with T = bool] has been implicitly deleted`

## static_assert

The second solution is to add a static assertion on `T` in `f()`. `static_assert` was introduced in C++11. The standard library has two techniques to check that `T` is not `bool`, one from C++11, the other from C++17.

**Since C++11**

C++11 introduced [the template structure `std::is_same`](https://en.cppreference.com/w/cpp/types/is_same), which does exactly what you think it does.

```cpp
#include <type_traits>

template <typename T>
T f(T t) {
    static_assert(not std::is_same<T, bool>::value, "T cannot be bool");
    return 2 * t + 3;
}
```

>`error: static_assert failed due to requirement '!std::is_same<bool, bool>::value' "T cannot be bool"`

**Since C++17**

C++17 introduced  the variable template `std::is_same_v<U, V>` as a shortcut for `std::is_same<U, V>::value`.

```cpp
#include <type_traits>

template <typename T>
T f(T t) {
    static_assert(not std::is_same_v<T, bool>, "T cannot be bool");
    return 2 * t + 3;
}
```

>`error: static_assert failed due to requirement '!std::is_same_v<bool, bool>' "T cannot be bool"`

Note: [variable templates were introduced in C++14](https://en.cppreference.com/w/cpp/language/variable_template).

## Concepts

**Since C++20**

[Concepts](https://en.cppreference.com/w/cpp/language/constraints) are one of the biggest features of C++20. We have two possibilities with concepts to forbid `T` from being `bool`.

### With a require clause

```cpp
#include <concepts>

template <typename T> requires (not std::same_as<T, bool>)
T f(T t) {
    return 2 * t + 3;
}
```

>`error: no matching function for call to 'f'`
>
>`note: candidate template ignored: constraints not satisfied [with T = bool]`
>
>`note: because '!std::same_as<_Bool, _Bool>' evaluated to false`

### With a custom concept

```cpp
#include <concepts>

template <typename T, typename U>
concept different_than = not std::same_as<T, U>;

template <different_than<bool> T>
T f(T t) {
    return 2 * t + 3;
}
```

>`error: no matching function for call to 'f'`
>
>`note: candidate template ignored: constraints not satisfied [with T = bool]`
>
>`note: because 'different_than<_Bool, _Bool>' evaluated to false`
>
>`note: because '!std::same_as<_Bool, _Bool>' evaluated to false`

## Conclusion

In my opinion, `=delete` has no advantages over the other techniques. I like `static_assert` because you can write a custom error message, but at the end of the day, I believe concepts are greater because they have a clearer semantic. And that normal: they were made exactly to express constraints of template parameters.

👇🏻 Leave a comment to tell which technique you use/prefer and why 😃

PS: the idea of this article comes from the discussion on ["Three ways to use the = delete specifier in C++"](https://dev.to/sandordargo/three-ways-to-use-the-delete-specifier-in-c-4lci) by [Sandor Dargo](https://dev.to/sandordargo) 👍🏻