Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[[no_unique_address]] with duplicate type #75

Open
vasama opened this issue Mar 31, 2019 · 2 comments
Open

[[no_unique_address]] with duplicate type #75

vasama opened this issue Mar 31, 2019 · 2 comments

Comments

@vasama
Copy link

vasama commented Mar 31, 2019

The current specification produces suboptimal layouts for classes where two non-static member variables of the same empty type use [[no_unique_address]].

In #49 and in the paper introducing the feature it was noted that in order to allow existing code to easily transition to [[no_unique_address]], the layout should be the same as if empty base classes had been used, where applicable.

In the case of two instances of the same type however, no base classes could have previously been used, as a class may not directly have two of the same base class.

Here an optimal layout is produced:

struct e {};
struct s {
	[[no_unique_address]] e a;
	[[no_unique_address]] e b;
	int c;
};
// 0: empty a
// 1: empty b
// 0: int c
// size: 4

The same layout can be produced, and is already being produced by GCC and Clang, without [[no_unique_address]]:

struct e {};
struct a : e {};
struct b : e {};
struct s : a, b {
    int x;
};

However moving the int variable to the front changes things:

struct e {};
struct s {
    int c;
    [[no_unique_address]] e a;
    [[no_unique_address]] e b;
};
// 0: int c
// 0: empty a
// 4: empty b
// size: 8

So it is this case where two non-static member variables of the same empty type, adorned with [[no_unique_address]], are placed not at the front of the class, which produces a suboptimal layout when another layout, already being used in other cases, would do.

@zygoloid
Copy link
Contributor

The current rule is consistent with the rule for base classes:

struct a { int n; };
struct e {};
struct x : e {};
struct y : e {};
struct s1 : x, y, a {}; // sizeof(s1) == 4
struct s2 : a, x, y {}; // sizeof(s2) == 8

If we used the s1 layout for s2, then converting all of s2's base classes into [[no_unique_address]] members would change its layout.

So we need to decide which we care more about: that a sequence of bases can be transformed into a sequence of [[no_unique_address]] members with no change in layout (which we can only guarantee in the absence of dynamic base classes, due to the special layout rules for virtual bases and primary base classes), or that we don't waste space on empty [[no_unique_address]] members whenever possible.

Here is perhaps a more compelling example justifying the current approach:

struct noncopyable {};
struct alloc : noncopyable {};
struct base : noncopyable { int n; };
struct data : noncopyable { int k; };
struct z : base {
  [[no_unique_address]] alloc x;
  data m;
};

It is desirable for the layout of z to match the layout of

struct z : base, alloc {
  data m;
};

... but if we consider placing alloc at offsets that would not be considered for a base class, we cannot guarantee that.

Perhaps we could consider additional offsets only for [[no_unique_address]] members that do not occur as part of a sequence of such members at the start of a class, on the basis that such cases have no corresponding base class layout to be compatible with. (And I'll open a separate issue that we should consider more offsets for empty subobjects in general.)

@zygoloid
Copy link
Contributor

Filed #77 to capture that we should consider nonzero offsets within the class when performing EBO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants