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)?
2
Answers
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 placestatic_assert(false)
in a template or in a branch of anif constexpr
statement, then it means that every possible instantiation of that template or thatif constexpr
branch will make the program ill-formed. When the compiler sees a template or anif 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 ofalways_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. Becausestatic_assert(always_false<T>)
does not satisfy the "no possible instantiation could be well-formed" criterion, it avoids the problem ofstatic_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.static_assert(false, ...)
is always evaluated, so will always fail immediately. You need the 1st parameter to be dependant on template evaluation so it get evaluated only if the otherif
branches evaluate as false.See Raymond Chen’s blog article on this topic for a deeper explanation:
How can I create a type-dependent expression that is always false?
In a nutshell: