I'm using std::variant
to specify the types of properties that an entity in my project may have, and stumbled upon this code from cppreference:
std::visit([](auto&& arg)
{
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int with value " << arg << '\n';
else if constexpr (std::is_same_v<T, long>)
std::cout << "long with value " << arg << '\n';
else if constexpr (std::is_same_v<T, double>)
std::cout << "double with value " << arg << '\n';
else if constexpr (std::is_same_v<T, std::string>)
std::cout << "std::string with value " << std::quoted(arg) << '\n';
else
static_assert(always_false_v<T>, "non-exhaustive visitor!");
}, w);
always_false_v
is defined as
template<class>
inline constexpr bool always_false_v = false;
I get that this checks at compile time if I'm handling all of the types in my variant which is pretty cool and helpful, but I'm puzzled as to why always_false_v<T>
is required.
If I remove a branch from the if
s, intellisense in Visual Studio immediately sets red squigglies because the static_assert
fails.
If I replace always_false_v<T>
with false
, intellisense doesn't complain but the static assert fails when I try to build.
Why is just false
not enough? I would expect the else
to never execute even at compile time, if it executes all the time, why is always_false_v<T>
not equivalent to false
(it looks like it's true
which surely it cannot be)?
This issue is discussed here. Jonathan Wakely's answer is particularly informative.
In short, because static_assert(false)
makes the program ill-formed, if you place static_assert(false)
in a template or in a branch of an if constexpr
statement, then it means that every possible instantiation of that template or that if constexpr
branch will make the program ill-formed. When the compiler sees a template or an if constexpr
branch that where every possible instantiation is ill-formed, it can reject the program even if such instantiations never occur.
However, when you have a construct of the form static_assert(always_false_v<T>);
, it is not true that every possible instantiation makes it ill-formed. There could be some specialization of always_false_v
that is true. Sure, in your program, you don't have such a specialization, but the point is that you could introduce one, and it could be after the definition of the template that references it. Because static_assert(always_false<T>)
does not satisfy the "no possible instantiation could be well-formed" criterion, it avoids the problem of static_assert(false)
.
The fact that static_assert(false)
makes the entire template ill-formed is particularly annoying, as there's no situation where the early diagnosis is actually helpful to the programmer. For this reason, the rules have been changed in C++23 to provide a special exemption for static assertions: static_assert(false)
will now only make the program ill-formed if it is actually instantiated.