4

While I was playing with constexpr function, I faced the following issue.

Accessing inactive member of union is undefined behaviour I know. But there is one exception for this rule.

According to C++23 standard [class.mem.general]/26,

In a standard-layout union with an active member (11.5) of struct type T1, it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2; the behavior is as if the corresponding member of T1 were nominated.

So according to the standard, following code has well-defined behaviour. (note: following code is taken from the standard as well)

struct T1 { int a, b; };
struct T2 { int c; double d; };
union U { T1 t1; T2 t2; };
int f() {
    U u = { { 1, 2 } }; // active member is t1
    return u.t2.c; // OK, as if u.t1.a were nominated
}

Because above code is well-defined, then it should be evaluated in compile time.

So I prepared the following code (Here is the compiler explorer link: https://godbolt.org/z/G4e3avMaz

#include <type_traits>

template <typename T, T value>
struct Test_Union {
    struct T1 {
        T item_1 ;
    } ;

    struct T2 {
        T item_1 ;
    } ;

    union Union {
        T1 t1 ;
        T2 t2 ;
    } ;

    static_assert(std::is_standard_layout_v<T1>) ;
    static_assert(std::is_standard_layout_v<T2>) ;
    static_assert(sizeof(T1) == sizeof(T2)) ;
    static_assert(sizeof(T1) == sizeof(Union)) ;

    static constexpr Union test_1 = {.t1 = {value}} ;

    constexpr T no_error_1 (void) {
        return test_1.t1.item_1 ;
    }

    constexpr T no_error_2 (void) {
        constexpr Union test_2 = {.t1 = {value}} ;
        return test_2.t1.item_1 ;
    }

    constexpr T error_1 (void) {
        return test_1.t2.item_1 ;
    }

    constexpr T error_2 (void) {
        constexpr Union test_2 = {.t1 = {value}} ;
        return test_2.t2.item_1 ;
    }
} ;

int main (void) {
    []() consteval {
        Test_Union<int, 123> test ;
        test.no_error_1() ;
    } () ;

    []() consteval {
        Test_Union<int, 123> test ;
        test.no_error_2() ;
    } () ;
    // consteval function is not a constant expression error
    []() consteval {
        Test_Union<int, 123> test ;
        test.error_1() ;
    } () ;

    // consteval function is not a constant expression error
    []() consteval {
        Test_Union<int, 123> test ;
        test.error_2() ;
    } () ;
}

When you compile the example in g++ or clang (I did), you get an compile time error for the last two consteval lambda which called error_1 and error_2 function.

The reasons are similar. For clang:

<source>:55:5: error: call to consteval function 'main()::(anonymous class)::operator()' is not a constant expression
   55 |     []() consteval {
      |     ^
<source>:35:16: note: read of member 't2' of union with active member 't1' is not allowed in a constant expression
   35 |         return test_1.t2.item_1 ;
      |                ^
<source>:57:9: note: in call to 'test.error_1()'
   57 |         test.error_1() ;
      |         ^~~~~~~~~~~~~~
<source>:55:5: note: in call to '[]() {
    Test_Union<int, 123> test;
    test.error_1();
}.operator()()'
   55 |     []() consteval {
      |     ^~~~~~~~~~~~~~~~
   56 |         Test_Union<int, 123> test ;
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~
   57 |         test.error_1() ;
      |         ~~~~~~~~~~~~~~~~
   58 |     } () ;
      |     ~~~~
<source>:60:5: error: call to consteval function 'main()::(anonymous class)::operator()' is not a constant expression
   60 |     []() consteval {
      |     ^
<source>:40:16: note: read of member 't2' of union with active member 't1' is not allowed in a constant expression
   40 |         return test_2.t2.item_1 ;
      |                ^
<source>:62:9: note: in call to 'test.error_2()'
   62 |         test.error_2() ;
      |         ^~~~~~~~~~~~~~
<source>:60:5: note: in call to '[]() {
    Test_Union<int, 123> test;
    test.error_2();
}.operator()()'
   60 |     []() consteval {
      |     ^~~~~~~~~~~~~~~~
   61 |         Test_Union<int, 123> test ;
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~
   62 |         test.error_2() ;
      |         ~~~~~~~~~~~~~~~~
   63 |     } () ;

Clang is complaining that accessing inactive member of union. But according to the cpp standard, it should be ok. Like accessing t2.item_1 should be the same as t1.item_1.

So what do you think? I couldn't come up with any logical answer why compilers show an error. The only think I came up is that It looks like a compiler bug.

0

1 Answer 1

6

From constant_expression, emphasis mine:

A core constant expression is any expression whose evaluation would not evaluate any one of the following language constructs:
[..]
9. an lvalue-to-rvalue implicit conversion or modification applied to a non-active member of a union or its subobject (even if it shares a common initial sequence with the active member)

So it is explicitly forbidden in constexpr.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for your answer. But there is no "modification" to non-active member of union or implicit conversion. just reading the value from the non-active member of union. Also @molbdnilo
"lvalue-to-rvalue implicit conversion"
Thanks you for your answer. I got it now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.