Mike Vine
Mike Vine

Reputation: 9852

Standard layout unions with common initial sequence to create checked-bitfields as drop in replacements for bitfields?

Given class.mem/28:

https://eel.is/c++draft/class.mem#general-28

Which states:

In a standard-layout union with an active member 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.

I think its valid to create a union with multiple (different) structs each with a uint64_t member:

template<some params>
struct CustomField
{
    void operator=(uint64_t newVal)
    {
        val = someCustomInsertFn(newVal);
    }

    operator uint64_t() const
    {
        return someCustomExtractFn(val);
    }

    uint64_t val; // Note that this is used in a union and so aliases with all other fields and the `all` below
};

Then:

union CheckedBitField
{
    struct { uint64_t all; }
    CustomField<some param> fieldOne;
    CustomField<some other param> fieldTwo;
};

The idea being this is a simple union with structure members each of which just has a uint64_t val.

But those custom fields have custom void operator=(uint64_t newVal) and operator uint64_t() const operators which can add additional checks over what you can get with a basic bitfield.

An example of the insert function is to add additional checks that the value you are writing is logically valid and/or wont be truncated.

So this code is valid - just like in a normal bitfield:

CheckedBitField b{};
b.fieldOne = 5; // This runs custom code.

Does adding those additional functions break the class.mem/28 rules and/or am I missing something?

Full runnable code - which adds the functionality to set the min/max value of a field: https://godbolt.org/z/ozz16GEP8

Upvotes: 0

Views: 95

Answers (1)

user17732522
user17732522

Reputation: 76859

b.fieldOne = 5; has undefined behavior already before anything in your quote could become relevant.

The fieldOne member is not active, therefore the corresponding member subobject is not in its lifetime and calling a non-static member function on it is therefore UB.

The assignment also won't start the lifetime and make the member active because = is neither the built-in operator nor a trivial assignment operator in this case. (That's the only exception where new or a function that implicitly creates objects isn't needed to change the active member of a union.)

And also, the quote specifically is about reading the inactive members, not writing to them, which is still UB regardless of whether the rule applies.

Upvotes: 0

Related Questions