Search code examples
c++c++17if-constexpr

Why is always_false_v required in this situation?


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)?


Solution

  • 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.