Search code examples
c++language-lawyerconstexprunionstype-punning

Constexpr function returning member of union: g++ vs. clang++: no diagnostics vs. error


Consider this code:

typedef union { float v; unsigned u; } T;
constexpr T x = { .u = 0 };
constexpr float f(void)
{
        return x.v;
}

Is this code valid?

Invocations:

$ g++ t506a.cpp -c -std=c++20 -pedantic -Wall -Wextra
<nothing>

$ clang++ t506a.cpp -c -std=c++20 -pedantic -Wall -Wextra
t506a.cpp:3:17: error: constexpr function never produces a constant expression
      [-Winvalid-constexpr]
constexpr float f(void)
                ^
t506a.cpp:5:9: note: read of member 'v' of union with active member 'u' is not allowed in a
      constant expression
        return x.v;
               ^
1 error generated.

Which compiler is correct?


Solution

  • Both compilers are correct, even though the code is ill-formed, because no diagnostic is required in the program you've shown. It's true that f can never be evaluated as a core constant expression, but in that case dcl.constexpr#6 applies:

    For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression, or, for a constructor, an evaluated subexpression of the initialization full-expression of some constant-initialized object ([basic.start.static]), the program is ill-formed, no diagnostic required.

    (emphasis mine)

    Since no diagnostic is required, GCC is allowed to not diagnose this.

    On the other hand, if you do attempt to evaluate f as a constant, e.g.

    constexpr float a = f();
    

    then it violates expr.const#5.10:

    An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

    • an lvalue-to-rvalue conversion that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;

    and indeed, both GCC and Clang diagnose this error, as required during constant evaluation.