Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

When we want to use a static_assert in a if constexpr we must make the condition dependent on some template parameter. Interestingly, gcc and clang disagree when the code is wrapped in a lambda.

The following code compiles with gcc, but clang triggers the assert, even if the if constexpr can't be true.

#include <utility>

template<typename T> constexpr std::false_type False;

template<typename T>
void foo() {

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

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

int main() {
    foo<int>();
}

Live example here.

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

So the question is: which compiler is right? I'd assume that gcc is correct because the condition in the static_assert is dependent on T, but I'm not sure.

like image 385
florestan Avatar asked Jan 08 '20 13:01

florestan


2 Answers

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.

like image 52
Barry Avatar answered Oct 19 '22 16:10

Barry


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

like image 1
Guillaume Racicot Avatar answered Oct 19 '22 15:10

Guillaume Racicot