always_false<T>
template <class T>
struct foo
{
static_assert(false, "must use correct specialization");
};
template <>
struct foo<int>
{
// valid use of foo<T> ...
};
Sometimes, good intentions are punished by the compiler:
<source>:4:19: error: static assertion failed: must use correct specialization
4 | static_assert(false, "must use correct specialization");
| ^~~~~
To be fair, it’s just doing its job:
If
bool_constexpr
returnstrue
, this declaration has no effect. Otherwise a compile-time error is issued, and the text of message, if any, is included in the diagnostic message.
Our intention was to trigger the static assertion only when foo<T>
is instantiated with a non-supported type.
However, while processing declarations, the compiler sees static_assert(false, ...)
, which it can immediately evaluate and thus issues an error.
In today’s (rather short) post, we will use type-dependent expressions to realize our intentions.
A simple always_false<T>
Our initial attempt does not work because the compiler can immediately evaluate the condition. Just making the expression more complex is not a solution:
static_assert(sizeof(int) == 3, "must use correct specialization");
Still generates an error immediately.
Side note: am I the only on who constantly tries to write
static_assert<cond>
instead ofstatic_assert(cond)
?
So, how can we “defer” the evaluation until the class is actually instantiated?
Using a similar solution to what we did in the dont_deduce<T>
post: taking away the compiler’s ability to reason about an expression without an actual type for T
.
The technical term for what we need is a type-dependent expression.
Instead of static_assert(false)
, you sometimes see the following patterns:
template <class T>
struct foo
{
static_assert(sizeof(T) != sizeof(T));
static_assert(sizeof(T) < 0);
static_assert(sizeof(T) + 1 == 0);
static_assert(false && sizeof(T));
// does not clutter error message for incomplete types:
static_assert(false && sizeof(T*));
};
Now, the conditions formally depend on T
, even if their actual value will always be false.
Still, this is enough to silence the compiler.
Only an actual instantiation will trigger the error:
foo<bool> f;
// leads to:
<source>: In instantiation of 'struct foo<bool>':
<source>:13:11: required from here
<source>:4:25: error: static assertion failed: must use correct specialization
4 | static_assert(false && sizeof(T), "must use correct specialization");
| ~~~~~~^~~~~~~~~~~~
In my libraries, I usually formulate this as a small helper for consistency and clarity of intent:
template <class... T>
constexpr bool always_false = false;
which is then used as:
template <class T>
struct foo
{
static_assert(always_false<T>, "must use correct specialization");
};
The definition of always_false
is variadic so that multiple types can be provided.
Use with if constexpr
I have two main use cases for always_false<T>
.
The first is the already mentioned class template specialization when the “base case” is not supported. A solution I sometimes see is to only declare but not define the template:
template <class T>
struct foo; // only define the specializations
However, the resulting error message will confuse users of your API:
<source>:10:11: error: aggregate 'foo<bool> f' has incomplete type and cannot be defined
10 | foo<bool> f;
| ^
The second use case is in combination with if constexpr
:
template <class T>
void foo()
{
if constexpr (std::is_same_v<T, int>)
{
// handle int case
}
else if constexpr (std::is_same_v<T, float>)
{
// handle float case
}
// ... other cases
else
{
static_assert(false, "T not supported");
}
}
This has the exact same problem: the static assertion triggers even without any use or instantiation of foo
.
Luckily it also has the same solution: static_assert(always_false<T>, "T not supported");
Summary
static_assert(false)
always immediately triggers (unless #ifdef
d) even if our intention is only to trigger on wrong instantiations.
The solution is to use type-dependent expressions and make the condition depend on our template parameters.
The main use cases are templated classes, where the “base case” should be forbidden and only “approved” specializations are allowed, and (chained) if constexpr
, where we want to communicate unsupported cases via static assertions.
Popular ad-hoc solutions include sizeof(T) + 1 == 0
or false && sizeof(T)
.
I usually prefer a simple helper that clearly communicates intent:
template <class... T>
constexpr bool always_false = false;
Additional discussion and comments on reddit.
(Title image from pixabay)