Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

is it compile bug? Accessing inactive member of union in constepr function causes compile error in compile time evaluation

While I was playin 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,

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

>Solution :

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading