Changing active member of union in constant expres

2019-04-10 10:26发布

问题:

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)); }.

回答1:

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