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

316 views Asked by At

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]]?

2

There are 2 answers

0
Brian Bi On BEST ANSWER

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.

0
Nicol Bolas On

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