Forbid a particular specialization of a template

Pierre Gradot - Jan 11 '21 - - Dev Community

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

template <typename T>
T f(T t) {
    return 2 * t + 3;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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 👍🏻

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player