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

```
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, 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`

:

```
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`

, which does exactly what you think it does.

```
#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`

.

```
#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.

## Concepts

**Since C++20**

Concepts 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

```
#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

```
#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++" by Sandor Dargo 👍🏻

## Discussion (8)

Small typo at the beginning

I also want to point out that the

`static_assert`

approach has a major drawback compared to the other ones. It prevents you to accurately detect if your function`f`

is callable with a`bool`

. Indeed with the`static_assert`

you have a function that is defined and takes part in overload resolution and simply produces an error when actually compiled.I you were using a library that, for some reason, wants to call your function with a

`bool`

if supported, else call with an`int`

, the`static_assert`

would fail to compile because the library would think that the function is callable with a`bool`

and attempt to do so before encountering the`static_assert`

. With the other 2 approach, the library would correctly detect that the function cannot be called with a`bool`

and will instead call it with an`int`

.Granted, this particular situation is unlikely to happen but there is a lot of similar situation in the standard library and in other library where something like that could occur. This is why

`static_assert`

is not used much in the standard library but instead SFINAE is used (hopefully replaced by concepts in the future).Thanks, I have corrected this mistake.

I completely understand the principle. But it is not clear to me how would you write such a code. Can you provide a basic example please?

Hmm, I wasn't able to implement such detection for a free function because function template don't have a type (only concrete instantiation do).

By wrapping the function inside a

`struct`

I can showcase what I was talking about (because now I can make a template that takes my`struct`

as a parameter).godbolt.org/z/z5a1h6

All in all maybe this is not really a problem with free function but only with member function (static or not).

Just a bit of nitpicking.

`=delete`

is available also since C++11 according to CppReferenceRegarding concepts, it's worth to note (?) that in case you only want to accept numbers (I'm a bit vague) you have 4 different ways:

I'll write a more detailed post about the 4 ways of concepts.

_Please note that the concept

`Number`

is incomplete, it accepts`char`

for example _This is not being "nitpicking": this is being "accurate". Thanks! I fixed that.

In fact, I missed this warning:

I have not really dived into concepts yet, so I am really interested by your future article!

PS: I have added a PS to my article above ; )

Gosh, I started that article, and it's already longer than the whole series on constness. I'll have to break it down :)

Not surprising XD

Thanks a lot for the PS :)

I have to start writing that article soon!