if constexpr with static_assert in lambda, which compiler is correct?

The usual rule here is [temp.res]/8:

The program is ill-formed, no diagnostic required, if: no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated

Once you instantiate foo<T>, the static_assert you have is no longer dependent. It becomes static_assert(false) - for all possible instantiations of the call operator of the generic lambda f. That's ill-formed, no diagnostic required. Clang diagnoses, gcc doesn't. Both are correct.

Note that it doesn't matter that the static_assert here is discarded.

It can easily be fixed by substituting False<T> by False<decltype(x)>.

This keeps the static_assert dependent within the generic lambda, and now we get into a state where there could hypothetically be a valid specialization, so we're no longer ill-formed, ndr.


From [stmt.if]/2 (emphasis mine)

If the if statement is of the form if constexpr, the value of the condition shall be a contextually converted constant expression of type bool; this form is called a constexpr if statement. If the value of the converted condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity ([temp.pre]), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.

Reading that one would think the static assert would be dropped, but this is not the case.

The static assert is triggered in the first phase of the template because the compiler know it's always false.

From [temp.res]/8 (emphasis mine)

The validity of a template may be checked prior to any instantiation. [ Note: Knowing which names are type names allows the syntax of every template to be checked in this way. — end note ] The program is ill-formed, no diagnostic required, if:

  • (8.1) no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, or

[...]

Yes indeed, your False<T> depends on T. The problem is that a generic lambda is itself a template, and False<T> is not dependent on any template parameter of the lambda.

For a T that False<T> is false, the static assert will always be false, no matter which template argument is sent to the lambda.

The compiler can see that for any instantiation of the template operator(), the static assert will always trigger for the current T. Hence the compiler error.

A solution for this would be to depend on x:

template<typename T>
void foo() {

    auto f = [](auto x) {
        if constexpr(x < 0) {
            static_assert(False<decltype(x)>, "AAA");
        }
    };

    f(std::integral_constant<int, 1>{});
}

Live example