When is it safe to use [[no_unique_address]] in C++?

107 views Asked by At

I understand what [[no_unique_address]] is for (this answer is helpful), but how can I tell whether it's safe to use?

I'm looking for a set of conditions that are sufficient to know it's safe to mark a class member with [[no_unique_address]]. For example, "it's always safe if the class doesn't compare the member's address to anything" (assuming that's true). Ideally the answer would also cite the standard to prove why its conditions are sufficient.

1

There are 1 answers

5
jacobsa On

According to [dcl.attr.nouniqueaddr] and [intro.object]/7 the effect of [[no_­unique_­address]] on a struct member is to make that member a potentially-overlapping subobject. So we can work backwards from that term to find what the standard says about these fields. Here are all of the mentions in N4868:

  • [intro.object]/8: being a potentially-overlapping subobject is a necessary condition for an object to have zero size, leaving out empty base classes.

    This is the basic reason for [[no_unique_address]], as discussed in the "what is [[no_unique_address]]" answer linked from the question. We can find further citations that involve zero/non-zero size objects:

    • [intro.object]/9 implies that subobjects of zero size with different types may have the same address. (Arguably it also says they may occupy the same bytes of storage, but presumably there are no such bytes.)

    • [intro.object]/9 also guarantees that the address of a subobject of zero size will be the address of some byte of storage occupied by the complete object to which the subobjects belongs.

    • [expr.rel]/4.2 guarantees ordering of pointers to subobjects within containing objects only for objects of non-zero size.

    • [class.prop]/3.7.2 imposes conditions on whether a type is a standard-layout class or not. To be honest I don't fully understand the wording here, but it's clear that this is relevant only when the type of consideration has a base class.

      (It seems to be missing a quanitifer. There can be multiple non-static data members of zero size, right?)

    • [meta.unary.prop]/4 says that std::is_empty<S> can be true even if S has non-static data members, as long as they have zero size.

  • [basic.life]/8 says an object is not transparently replaceable by another object if either is a potentially-overlapping subobject. This means that you can't destroy and recreate potentially-overlapping subobjects in place and expect existing pointers/references/names to work.

  • [basic.types.general]/2 says that you can't copy the storage of a potentially-overlapping subobject out as bytes and then back into place and expect it to have the same value.

  • [basic.types.general]/3 says that you can't copy storage from one object as bytes over the storage of another object and expect the second object to have the same value as the first if either is a potentially-overlapping subobject.

  • expr.sizeof says that sizeof(S) for type S may be larger than the size of a potentially-overlapping subobject of type S. Similarly, I think, when applied to the subobject itself rather than its type.

The only other relevant mention of [[no_unique_address]] is in [class.mem.general]/22, where it says that the common initial sequence between two standard-layout structs is sensitive to the presence of the attribute. This affects type punning for reads via unions is allowed.

So, in something like English, the following conditions are sufficient:

  • You don't mind the address of the object aliasing the address of another object. (This can only happen for objects of different types, it seems.)
  • You don't need a guarantee on ordering of pointers involving the member.
  • You don't care about whether a containing object with the field's type as a base class is standard layout or not.
  • You don't mind if a containing object is empty according to std::is_empty.
  • You don't play games with object lifetimes and expect pointers/references/names to continue working.
  • You don't copy bytes from character arrays and expect values to match.
  • You don't mind sizeof giving a too-large answer for the member.
  • You don't need to use unions to do type punning.