Changing active member of union in constant expressions
Asked Answered
I

1

8

Playing with constexpr and union I found out, that I can't change active member of an union in constexpr. Just one exception: union of empty classes.

constexpr bool t()
{
    struct A {};
    struct B {};
    union U { A a; B b; } u{};
    u.a = A{};
    u.b = B{};
    return true;
}
static_assert(t());

constexpr bool f()
{
    struct A { char c; };
    struct B { char c; };
    union U { A a; B b; } u{};
    u.a = A{};
    u.b = B{}; // error originating from here
    return true;
}
static_assert(f());

First function may produce constant expression. But the second can't. Hard error says:

main.cpp:23:15: error: static_assert expression is not an integral constant expression
static_assert(f());
              ^~~
main.cpp:20:11: note: assignment to member 'b' of union with active member 'a' is not allowed in a constant expression
    u.b = B{};
          ^
main.cpp:20:9: note: in call to '&u.b->operator=(B{})'
    u.b = B{};
        ^
main.cpp:23:15: note: in call to 'f()'
static_assert(f());
              ^
1 error generated.

LIVE EXAMPLE

1.) Is it possible to change active member of union in constant expressions?

I tried to destruct active member, but it is not allowed, due to destructors are not constexpr in general. Also I tried to use placement operator new (::new (&u.b) B{2};), but also unusccessfull. reinterpret_cast also not allowed in constant expressions. Altering members of common initial subsequence prohibited too.

2.) Are there a ways to make mutable (in sense of changing active alternative type) literal boost::variant-like type? How looks like its storage if it possible?

3.) Is it undefined behaviour to make assignment to non-active members of union of trivially copy-assignable types at runtime? Is it undefined behaviour to construct non-active member of union of trivially-copyable types using placement operator new avoiding preliminary destruction of active member at runtime?

ADDITIONAL:

I can change entire literal type union, but not its non-active member:

constexpr
bool
f()
{
    struct A { char c; };
    struct B { char c; };
    union U 
    {
        A a; B b; 
        constexpr U(A _a) : a(_a) { ; }  
        constexpr U(B _b) : b(_b) { ; }  
    };
    U a(A{});
    a.a = A{}; // check active member is A
    U b(B{});
    b.b = B{}; // check active member is B
    a = b;
    a = B{}; // active member is B!
    return true;
}
static_assert(f());

LIVE EXAMPLE

Therefore for literal type variant of trivially copyable types conversion assignment operator would be template< typename T > constexpr variant & operator = (T && x) { return *this = variant(std::forward< T >(x)); }.

Ifill answered 26/11, 2015 at 10:32 Comment(4)
What are your compilation flags? I couldn't reproduce it on Coliru.Engagement
No reproAngelangela
@n.m. I use clang 3.7.0Ifill
This area of the standard is rather messy. Some revision of P0137R0 should hopefully clean it up a little bit.Cordellcorder
L
5

Disclaimer: "active" is defined in P0137R0.

Is it possible to change active member of union in constant expressions?

Not directly, since modifying a non-active member is prohibited - [expr.const]/(2.8):

— an lvalue-to-rvalue conversion (4.1) or modification (5.18, 5.2.6, 5.3.2) that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;

However, this wording seems defective, since it's indeed possible to "modify" a non-active member by assignment of another union object as shown in your example. In fact, the copy assignment operator performs a copy of the underlying bytes and the internal information about the active member:

The implicitly-defined copy assignment operator for a union X copies the object representation (3.9) of X.


Is it undefined behaviour to make assignment to non-active members of union of trivially copy-assignable types at runtime?

That's presumably fine for objects of a trivially copyable class type, since those have trivial destructors and copy constructors/assignment operators. Although underspecified, CWG #1116 seems to imply that it's intended to work:

We never say what the active member of a union is, how it can be changed, and so on. The Standard doesn't make clear whether the following is valid:

union U { int a; short b; } u = { 0 };
int x = u.a; // presumably this is OK, but we never say that a is the active member
u.b = 0;     // not clear whether this is valid
Latricelatricia answered 26/11, 2015 at 11:9 Comment(2)
Ping: Changing the active member of a union in constant expressions is now allowed in C++20. :)Lancelancelet
Yes, original example is accepted in GCC and MSVC in C++20 mode: gcc.godbolt.org/z/Trj7833rK And Clang refuses it probably because of this bug: github.com/llvm/llvm-project/issues/51130Tubercular

© 2022 - 2024 — McMap. All rights reserved.