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 ifs, 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 placestatic_assert(false)in a template or in a branch of anif constexprstatement, then it means that every possible instantiation of that template or thatif constexprbranch will make the program ill-formed. When the compiler sees a template or anif constexprbranch 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_vthat 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.