Skip to content

[class.mem.general] Fix wording regarding layout-compatible types#8787

Open
Halalaluyafail3 wants to merge 1 commit intocplusplus:mainfrom
Halalaluyafail3:main
Open

[class.mem.general] Fix wording regarding layout-compatible types#8787
Halalaluyafail3 wants to merge 1 commit intocplusplus:mainfrom
Halalaluyafail3:main

Conversation

@Halalaluyafail3
Copy link
Contributor

According to the current wording:

struct A{int x;};
struct B{int x;void foo(){}};

These types are not layout-compatible because the member function is not included in the common initial sequence. Moreover, even if A had the same member function it would not be included in the common initial sequence so B cannot be layout-compatible with any other type. There are more related issues:

union C{char x;};
union D{alignas(2)char x;};

The current wording for layout-compatible unions does not mention alignment, so the current wording says these types are layout-compatible even though they clearly ought not to be.

union E{int x:4;};
union F{int x:8;};

The current wording for layout-compatible unions does not mention bit-field widths, so the current wording says these types are layout-compatible even though they clearly ought not to be.

union G{int x;};
union H{[[no_unique_address]]int x;};

The current wording for layout-compatible unions does not mention no_unique_address, so the current wording says these types are layout-compatible even though they clearly ought not to be if no_unique_address is supported.

union I{char x;};
union J{char x;int:10;};

The current wording for layout-compatible unions does not mention unnamed bit-fields as they are not members, so the current wording says these types are layout-compatible even though they clearly ought not to be.

struct K{char x;};
struct alignas(2)L{char x;};

The current wording for layout-compatible classes and layout-compatible unions (layout-compatible unions are not layout-compatible classes, despite being classes and layout-compatible) does not mention alignas applied to the type, so the current wording says these types are layout-compatible even though they clearly ought not to be.

struct M{char x;};
struct N{alignas(1)char x;};

The current wording does not clearly define what "same alignment requirements" means, there is some compiler divergence here about whether or not these types are layout-compatible.

I have only proposed changes to the first issue since it seems the most clear and requires only a small change to fix. If any of the other issues listed here could be fixed editorially too, I would be willing to include fixes for them too.

It would not make sense if a non-union class having member functions or static data members would stop it from being layout-compatible with other types.
@Halalaluyafail3
Copy link
Contributor Author

Halalaluyafail3 commented Mar 12, 2026

It seems like the layout-compatibility of C and D, as well as K and L can be explained by "same alignment requirements" in the definition of common initial sequence including the alignment of the types themselves. Though that creates new issues:

struct O{char x;};
struct alignas(2)P{char x;};
struct Q{A y;int z;};
struct R{B y;int z;};
static_assert(alignof(int)>1);

Does the declaration of the later z members in both Q and R cause the alignment requirements of each y member to be greater than one as a result? Because of pointer-interconvertibility the y members must have the same alignments as their respective containing classes. If yes, then Q and R are layout-compatible. Otherwise, Q and R are not layout-compatible.

@frederick-vs-ja
Copy link
Contributor

I'll be glad if some fixes can be considered editorial.

These types are not layout-compatible because the member function is not included in the common initial sequence.

This seems to be an unintended bug in the wording update of CWG1719.

(layout-compatible unions are not layout-compatible classes, despite being classes and layout-compatible)

Looks like an oversight in #700 to me. I believe layout-compatible unions should also be layout-compatible classes. CC @AaronBallman @zygoloid.


For C and D, I think it's fine to keep them layout-compatible as aliasing tricks in unions can certainly work. Given they have different alignment requirements, they can cause layout-incompatibility when used as members of other structs, which is counterintuitive but still fine to me. Same for K & L.


Remaining questions will require CWG issues, IMO.

For Q and R, I think it's more plausible to say the alignment requirement of the leading y member is not strengthened via the enclosing class, so Q and R are not layout-compatible. See [dcl.align] p4. But perhaps we should properly define alignment requirements of non-static data members somewhere.

@jensmaurer
Copy link
Member

There's nothing editorial here.

@jensmaurer jensmaurer added cwg Issue must be reviewed by CWG. not-editorial Issue is not deemed editorial; the editorial issue is kept open for tracking. labels Mar 12, 2026
@frederick-vs-ja
Copy link
Contributor

I think the status quo for [[no_unique_address]] (G and H) is also fine.

Consider this more realistic example, where the tail padding of H2 can be reused on Itanium ABI.

// tail-padded in common implementations, made non-POD due to weirdness of Itanium ABI
struct Padded { int n{}; char c{}; };
union G2 { Padded x; };
union H2 { [[no_unique_address]] Padded x; };

In order to reuse the padding of H2 (propagated from Padded), we need to put an H2 subobject into a struct, and either the H2 member or something wrapping it need to be marked [[no_unique_address]]. IIUC, it's inevitable for the reusing to break the common initial sequence.

When there's no outer [[no_unique_address]] applied and thus no reusing, it's fine to treat consider [[no_unique_address]] in H2 not to affect layout-compatibility.

@Halalaluyafail3
Copy link
Contributor Author

I think the status quo for [[no_unique_address]] (G and H) is also fine.

Status quo of the wording, or status quo of the existing implementations? Both GCC and Clang reject that G and H are layout-compatible, MSVC of course accepts it since it ignores no_unique_address.

Consider this more realistic example, where the tail padding of H2 can be reused on Itanium ABI.

// tail-padded in common implementations, made non-POD due to weirdness of Itanium ABI
struct Padded { int n{}; char c{}; };
union G2 { Padded x; };
union H2 { [[no_unique_address]] Padded x; };

In order to reuse the padding of H2 (propagated from Padded), we need to put an H2 subobject into a struct, and either the H2 member or something wrapping it need to be marked [[no_unique_address]]. IIUC, it's inevitable for the reusing to break the common initial sequence.

When there's no outer [[no_unique_address]] applied and thus no reusing, it's fine to treat consider [[no_unique_address]] in H2 not to affect layout-compatibility.

As far as I understand, tail padding like this cannot be used by used later by other non-empty objects: https://eel.is/c++draft/intro.object#10. For example:

//same Padded as before
struct S{[[no_unique_address]]Padded x;char y;};

For the members x and y to overlap in storage, one of the following conditions must be followed: "one is nested within the other", "at least one is a subobject of zero size and they are not of similar types", or "they are both potentially non-unique objects". x and y are obviously not nested within the other member. An object of type S can obviously be created that is not potentially non-unique. y does not have zero size because it is not a class type. x does not have zero size because it contains subobjects of non-zero size. Therefore x and y cannot overlap in storage, and Itanium letting them overlap seems more like an ABI bug.

@jensmaurer
Copy link
Member

jensmaurer commented Mar 12, 2026

I don't think [[no_unique_address]] on a union member has any effect; we maybe should make that ill-formed.

Having two unions with different alignment requirements doesn't seem to cause layout difference for the union itself:

union C { char x; };
union D { alignas(2) char x; };

If you put those in a "common initial sequence" situation, those are not layout-compatible at that level, because the unions themselves have different alignment requirements (indirectly caused by different alignment requirements of their members).

I agree that bit-field lengths need to be compared for layout compatibility of union members.

@Halalaluyafail3
Copy link
Contributor Author

I don't think [[no_unique_address]] on a union member has any effect; we maybe should make that ill-formed.

struct T{int x;char c{};};
union U{int y;[[no_unique_address]]T t;};
struct V{[[no_unique_address]]U u;char z;};

With Itanium the size of V changes depending upon whether or not the member t of T is declared with no_unique_address.

@frederick-vs-ja
Copy link
Contributor

//same Padded as before
struct S{[[no_unique_address]]Padded x;char y;};

For the members x and y to overlap in storage, one of the following conditions must be followed:

The current wording doesn't restrict conditions for "overlapping" the whole members in that way. For such S::x and S::y, the wording says "otherwise, they have distinct addresses and occupy disjoint bytes of storage".

IIUC, given such an S object s, on Itanium ABI:

  • s.x is a potentially-overlapping subobject,
  • s.x doesn't occupy its tail padding bytes,
  • s.y is located at a tail padding byte of s.x,
  • s.y still has higher address than s.x.

I believe this is conforming.

Status quo of the wording, or status quo of the existing implementations?

I think the status quo of the wording (in the case of G and H or G2 and H2) is fine enough, and implementations should probably be fixed. It might be a bit more complicated to ignore [[no_unique_address]] on union members for the purpose of layout-compatibility (__is_layout_compatible etc.), but I believe it's better not to sacrifice layout-compatibility if not necessary.

@Halalaluyafail3
Copy link
Contributor Author

  • s.x doesn't occupy its tail padding bytes,

This part I think the standard does not state very clearly, I created cplusplus/CWG#868 for that.

Status quo of the wording, or status quo of the existing implementations?

I think the status quo of the wording (in the case of G and H or G2 and H2) is fine enough, and implementations should probably be fixed. It might be a bit more complicated to ignore [[no_unique_address]] on union members for the purpose of layout-compatibility (__is_layout_compatible etc.), but I believe it's better not to sacrifice layout-compatibility if not necessary.

I suppose it does not really matter because for no_unique_address on the members of a union to matter, the union which contains it must be part of a non-union class (possibly as a submember) and no_unique_address is used on the member which is the union or contains the union. In which case the use of no_unique_address would prevent layout-compatibility. I do not see the use case for it though, I think CWG should decide what is the intent here.

@jensmaurer
Copy link
Member

I've come around to agreeing that [[no_unique_address]] on union members has an effect, at least for the "re-use tail padding" examples posted here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cwg Issue must be reviewed by CWG. not-editorial Issue is not deemed editorial; the editorial issue is kept open for tracking.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants