Can padding of bases or [[no_unique_address]] members be used to store other bases/members?

Viewed 100

The note in [dcl.attr.nouniqueaddr] says:

[Note 1: The non-static data member can share the address of another non-static data member or that of a base class, and any padding that would normally be inserted at the end of the object can be reused as storage for other members. — end note]

(bold mine)

Since the only normative effect of no_unique_address is making the object potentially-overlapping, which base classes automatically are, the same effects should apply to base classes.

This appears to contradict [intro.object]/9

Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types; otherwise, they have distinct addresses and occupy disjoint bytes of storage.

This seemingly permits overlap only if one of the objects is zero-size (is potentially-overlapping and empty).

And indeed, neither GCC, Clang, nor MSVC allow non-empty objects to overlap: run on gcc.godbolt.org

struct A
{
    int x;
    short y;
};

struct B
{
    short b;
};

struct C : A, B {};

struct D
{
    [[no_unique_address]] A a;
    [[no_unique_address]] B b;
};

// Those assertions pass, but I expected the size to be `sizeof(int) * 2` instead.
static_assert(sizeof(C) == sizeof(int) * 3);
static_assert(sizeof(D) == sizeof(int) * 3);

There's also [basic.types.general]/2 that governs memcpy-ing trivially copyable types:

For any object (other than a potentially-overlapping subobject) of trivially copyable type T, ... the underlying bytes ... can be copied into an array of char ...

(bold mine)

If only empty objects can overlap, then it's unclear why it bans all potentially-overlapping objects and not only the empty ones.

What's going on here? Can non-empty potentially-overlapping objects actually overlap, and is there a way to make compilers do so without extensions?


Found the proposal that added [[no_unique_address]]. The plot thickens:

Does this allow reuse of tail padding?

Tail padding reuse is permitted for base classes, so it's also permitted for members with the attribute.

And the "disjoint bytes of storage" part was added by this very proposal, and didn't exist before. A regression perhaps?

1 Answers

Potentially-overlapping subobjects can overlap. What cannot happen is overlap between two potentially-overlapping subobjects if neither of them has zero size. That's what [intro.object]/9 is saying.

That is, D::a is potentially overlapping, but it cannot overlap with D::b. If there were a hypothetical D::empty_class that was also potentially overlapping, then D::a could actually overlap with it.

"Potentially overlapping" is only "potentially"; whether any actual overlap happens with any other potentially overlapping object depends on the circumstances.

But then what's the difference between non-empty PO and non-PO?

The difference is intent.

Since the benefit of no_unique_address only appears if the type in question is empty, why even allow the attribute on non-empty types?

Because your code may not know if a type is empty. That type could be provided to your class by the user as a template parameter (the allocator given to containers, for example). You wouldn't want your code to break just because the user provided a non-empty type. What you want is to take advantage of the case when the user does provide an empty type.

Plus, there's the fact that what constitutes an "empty type" is actually somewhat implementation-dependent. So some compilers may give you the optimization while others do not.

As such, provisions must be made for POs that are not zero-sized. Since the intent of declaring a subobject to be no_unique_address is that you will treat it as if it were overlapping (even if it doesn't happen to), the prohibition on using them in trivial copies merely holds you to your stated intent.

Related