skip to Main Content

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

2

Answers


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

    Login or Signup to reply.
  2. 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 other if 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:

    One of the things I had to do was prevent compilation from succeeding if the lambda was called incorrectly. I had a chain of if constexpr tests for the valid cases, and I needed to put a static_assert in the else that said “You should never get here.”

    However, this does not compile because [static_assert(false,...)] fails immediately.

    The reason is that the controlling expression for the static_assert is not dependent upon the type of the arguments, and therefore it is evaluated when the lambda is compiled, not when the lambda is invoked (and the implicit template instantiated).

    In order to defer the static_assert to instantiation, we need to make it type-dependent.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search