5

Below is excerpted from cppref but reduced to demo:

#include <iostream>
 
struct Empty {}; // empty class

struct W
{
    char c[2];
    [[no_unique_address]] Empty e1, e2;
};
 
int main()
{ 
    std::cout << std::boolalpha;

    // e1 and e2 cannot have the same address, but one of them can share with
    // c[0] and the other with c[1]
    std::cout << "sizeof(W) == 2 is " << (sizeof(W) == 2) << '\n';
}

The documentation says the output might be:

sizeof(W) == 2 is true

However, both gcc and clang output as follows:

sizeof(W) == 2 is false

Is it implementation-defined that how to deal with [[no_unique_address]]?

xmllmx
  • 39,765
  • 26
  • 162
  • 323
  • 2
    Aren't almost all decisions about struct padding and alignment up to the implementation? – Nathan Pierson Dec 01 '21 at 16:06
  • 1
    attributes in general... – Jarod42 Dec 01 '21 at 16:06
  • 1
    The info is right in the link to the attribute: *This means that if the member has an empty type (e.g. stateless Allocator), **the compiler may optimise it to occupy no space**, just like if it were an empty base.* – NathanOliver Dec 01 '21 at 16:08
  • I would say that compilers use same algo than EBO, so doesn't reorganize `[[no_unique_address]]` members (even if they would be allowed). – Jarod42 Dec 01 '21 at 16:08
  • 1
    both gcc and clang manage to optimize it if `e1`& `e2` are before `c` - [godbolt](https://godbolt.org/z/hha6Eoqe8). But in the end it's up to the compilers how or even if they want to take advantage of `[[no_unique_address]]` attributes. – Turtlefight Dec 01 '21 at 17:55

2 Answers2

7

See [intro.object]/8:

An object has nonzero size if ... Otherwise, if the object is a base class subobject of a standard-layout class type with no non-static data members, it has zero size. Otherwise, the circumstances under which the object has zero size are implementation-defined.

Empty base class optimization became mandatory for standard-layout classes in C++11 (see here for discussion). Empty member optimization is never mandatory. It is implementation-defined, as you suspected.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
6

Yes, virtually all aspects of no_unique_address are implementation-defined. It is a tool to allow for optimizations, not to enforce them.

That being said, you should never assume that no_unique_address will work when you attempt to have two subobjects with the same type. The standard still requires that all distinct subobjects of the same type have different addresses, no_unique_address or not. And while it is possible that the compiler could assign these empty subobjects distinct addresses by radically reordering the members... they're pretty much not going to do that.

Your best bet for reasonably taking advantage of no_unique_address optimizations is to never have two subobjects of the same type, and try to put all possibly empty members first. That is, you should expect implementations to assign an empty no_unique_address member to the offset of the next member (or to the offset of the containing struct as a whole).

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982