template<typename T>
class S : private T
{
int my_member;
};template<typename T>
struct U
{
[[no_unique_address]] T t;
int my_member;
};struct empty {};
struct A
{
[[no_unqiue_address]] empty x;
empty y;
};struct B
{
empty y;
};
struct C
{
[[no_unique_address]] empty x;
B b;
};struct EA {}; //Empty
struct EB : EA {}; //Also empty.
struct EC : EA, EB {};
struct ED : EA
{
EB b;
};On Wednesday, 6 December 2017 14:39:39 PST Nicol Bolas wrote:
> That is not true for this:
>
> template<typename T>
> struct U
> {
> [[no_unique_address]] T t;
> int my_member;
> };
>
> I have no guarantees that `U<T>` will have optimal packing.
Why wouldn't it have optimal packing?
Are you thinking of a compiler that does
not implement the attribute?
> So, when deciding between implementing my code as `S` or `U`, why would I
> ever pick `U` when `S` is the one that has explicit standard requirements?
T might be final.
On Wednesday, 6 December 2017 15:08:13 PST Nicol Bolas wrote:
> By your reasoning, why bother having EBO as part of standard layout at all?
> After all, why wouldn't a compiler optimize away empty base classes?
I'm not sure I agree with your assertion:
> C++11's standard layout rules effectively require EBO in a certain set of
> limited circumstances. If your class follows standard layout rules, the
> standard guarantees that the base class will not disturb the layout of the
> class's members.
First of all, this excludes non-std-layout.
And we've seen it excludes final
types too. A third case is when you need two non-unique-address members of the
same type: you can't directly derive twice from the same class.
So there are
already three cases when you can't use a base for no-address. The attribute
was created for those cases.
Second, can you explain your reasoning why you think an empty base of a
stdlayout class needs to have effective size zero?
A class with a base is not
possible in C and therefore cannot be shared with a C library, even though it
may follow all the rules required for POD.
So why is it required that:
struct empty {};
struct A : empty
{
int i;
};
sizeof(A) == sizeof(int)
offsetof(A, i) == 0
struct other_empty {};
struct B : other_empty, A {};
struct C : A, other_empty {};
struct my_int { int i; };
struct D : my_int {};Before C++11, empty base optimization was a commonly-implemented concept, but there was no actual requirement for it in the standard. C++98/03 permitted it, but it did not require it.
C++11's standard layout rules effectively require EBO in a certain set of limited circumstances. If your class follows standard layout rules, the standard guarantees that the base class will not disturb the layout of the class's members. This means that you know this data structure will have the most optimal packing possible:template<typename T>
class S : private T
{
int my_member;
};
For any type `T` where `is_empty_v<T>` is true, `S<T>` will provide optimal packing because the standard explicitly requires implementations to do so.
That is not true for this:template<typename T>
struct U
{
[[no_unique_address]] T t;
int my_member;
};
I have no guarantees that `U<T>` will have optimal packing.
So, when deciding between implementing my code as `S` or `U`, why would I ever pick `U` when `S` is the one that has explicit standard requirements?
And if `U` is not the obviously correct choice... what good is the feature?
It's not fixing the problem it claims to because it lacks the guarantees that standard layout provides with EBO.
So I make the claim that without some kind of guarantee of optimization, this feature fails to achieve its goals.
So, what would it take to fix this? Well, one big annoyance with P0840 is that it is defined as an attribute. And it stakes out its position on the validity of defining such a thing the idea that, so long as the code functions the same whether the attribute is implemented or not, then it's fine to use an attribute.
Following that rule (which, let's be frank, only exists as an expedient means of getting the proposal through the committee, not because it makes the feature better in some way)
makes it impossible to ensure layout compatibility to between `S<T>` and `U<T>`. But that doesn't mean we cannot have some reasonable guarantees on whether `U<T>::t` is zero-sized.
When I was thinking about my own zero-sized idea, discussed here, I came upon the whole "unique identity rule" issue. Namely, that every object of the same type T must have a unique address. None of my solutions to that issue ever really pleased me. This proposal kind of sidesteps that problem, since the attribute is effectively saying "this subobject does not have to be uniquely identified, so it may be zero sized", rather than my approach of "this subobject is zero sized and therefore may not be uniquely identified".
Of course, that's part of the problem: users don't actually care about unique identity; they care about the size.
I brought up my idea because, while investigating it, I happened upon a solution that seems to dovetail very closely with this proposal. But first, let's look at the scope of the problem. Let's say you have this type:struct empty {};
struct A
{
[[no_unqiue_address]] empty x;
empty y;
};
While `x` does not have to have a unique address, `y` does. And therefore `y`'s address must be unique from `x`'s. And since we're not allowed to reorder members, the compiler must include space for `x`.
OK, fine. What about this:struct B
{
empty y;
};
struct C
{
[[no_unique_address]] empty x;
B b;
};
Neither `B::y` nor `C::b` is declared to not have a unique address. But if the compiler does not assign a location to `C::x`, then it may overlap with `C::B::y`, which is not allowed. And therefore, `C::x` must have an address.
Note that the C++11 standard layout rules have the exact same problem with base classes:struct EA {}; //Empty
struct EB : EA {}; //Also empty.
struct EC : EA, EB {};
struct ED : EA
{
EB b;
};
By the (amended) rules of standard layout, neither `EC` nor `ED` is standard layout. Why? For `EC`, there are two base class subobjects of the type with the same type, and therefore they must have the same address. For `ED`, the empty base class `EA` potentially overlaps with `EC::b`'s base class. And this is actually recursive, through all of the base class subojects of the type and all of the base class subobjects of the first member of the type.
So I think we can effectively piggyback off of this rule.
I suggest we declare the following:
1. A type is not standard layout if any of its potentially overlapping subobjects are of the same type.
2. A type is not standard layout if any of its potentially overlapping subobjects are of the same type as the type of its first non-"no_unique_address" subobject or any potentially overlapping subobjects of that subobject.
3. All subobjects which are empty types that declared with the `no_unique_address` attribute are always zero sized if their containing type is standard layout.
4. A standard layout type with all of its members declared `no_unique_address` is empty.
One might argue that this now makes the `no_unique_address` attribute affect the visible behavior of the program. But I would argue that it already does so. `is_empty_v` is explicitly defined by the P0840 wording to be implementation-defined as to whether `no_unique_address` will affect the emptiness of a particular type. So there will be types where `is_empty_v` is true on some implementations and false on others.
Furthermore, a standard type still has to fulfill all of the other requirements of standard layout. So if a type `is_standard_layout` under the `no_unqiue_address` rules, then it would still be standard layout under the rules without the attribute. Similarly, it will not affect types that are still standard layout under the old rules.
Lastly, this rules do not change the layout compatibility rules and the common initial sequence rule. So these rules don't mean that `no_unique_address` is ignored by layout compatibility; it only ensures that if they're standard layout, the subobjects are guaranteed to be zero sized.
I came up with these rules somewhat off the cuff, so feel free to let me know where I went wrong. This all sounds implementable to me, but I haven't written the code for it.
On Wednesday, 6 December 2017 20:44:24 PST Nicol Bolas wrote:
> > First of all, this excludes non-std-layout.
>
> But it includes standard layout. Which means there is at least *some class*
> of types where it undeniably works. A set that certainly includes something
> is better than a set that may not include anything.
If it's a QoI issue, you get to blame your vendor.
For example, when I implemented code in Qt that used <chrono>, I got to blame
Microsoft for not adding __has_include until VS 15.3 (there was a thread even
in std-discussions about that).
> Despite the name, `no_unique_address` does not spare implementations from
>
> the "unique identity rule". From P0840R1's version of [intro.object]/8:
> > Unless an object is a bit-field or a subobject of zero size, the address
>
> of that object is the address of the first byte it occupies. Two objects
> with overlapping lifetimes that are not bit-fields may have the same
> address if one is nested within the other, or if at least one is a
> subobject of zero size and *they are of different types*; otherwise, they
> have distinct addresses and shall occupy disjoint bytes of storage.
>
> Emphasis added. If you add two members of the same type which use
> `no_unique_address`, they *must* have distinct addresses and distinct
> memory storage. And if you add two members of the same type, one which uses
> the attribute and the other which doesn't, the implementation *must* give
> them distinct addresses and distinct memory storage.
Well, one easy work around to that is to share the address of another member
in the larger structure.
In any case, why must this restriction apply to [[no_unique_address]] ? If we
don't want a unique address in the first place, why must it still be unique
among sub-objects of the same type?
> FYI: This rule is why the standard layout rules have that incredibly
> complicated bit about base classes and the first member subobject (and the
> first member subobject of that type, recursively) having the same type.
> Because the standard layout rules want to force EBO, and therefore are
> defined by the circumstances that cannot violate the unique identity rule.
I never understood that either. If the base and the first member aren't empty
classes, they obviously have different addresses. Why isn't that standard
layout?
If they are empty but no [[no_unique_address]] is in effect, then they have
different addresses. Why isn't that standard layout?
By the way, note how [[no_unique_address]] implies non-standard layout.
So a
type's actual classification ought to change with an attribute. This alone may
be a reason not to use them.
> > Second, can you explain your reasoning why you think an empty base of a
> > stdlayout class needs to have effective size zero?
>
> I'm not sure I understand the question. Whether it "needs to" or not is
> essentially sophistry because it already does. It did back in C++11; that's
> a done deal. We're talking about bringing the same power to member
> subobjects.
I'm asking what in the standard makes EBO mandatory. The "O" in EBO would
imply otherwise.
> If you're asking why that change was made... well, read the original paper
> on standard layout.
> <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2342.htm>
The paper says in the footnote "This ensures that two subobjects that have the
same class type and that belong to the same most-derived object are not
allocated at the same address ([expr.eq]).". It doesn't explain why they would
be allocated at the same address in the first place. It's clear that the
restriction makes it so the same-allocation cannot happen, but doesn't say how
it could otherwise happen.
> Now to be completely fair, standard layout rules don't actually require EBO
> per-se. Instead, it requires that empty base classes not disturb the layout
> of the members of the type. Theoretically, empty base classes can take up
> space after all of the non-static data members. However, as I'll
> demonstrate below, it's very hard to implement the rules of standard layout
> types without making subobjects zero sized in at least some cases.
My question is why can't you consider the base as a sub-object of size 1, and
thus the first member of the derived class is at 2 + alignment?
struct empty {};
struct A : empty {int i;};struct B {int i;};> Also, sharing objects between C and C++ does not (necessarily) require the
> objects to be defined in the same way. While the C++ object model does not
> support having C create C++ objects, if you create a C++ object that
> matches the layout of a C type, then C's object model can *receive* and
> manipulate that object just fine.
I'd say that's extremely fragile and should not be counted on. If you want to
share an object between C and C++, use the C declaration of the type. Anything
else is tempting UB.
Unless the standard begins to *explicitly* say that a given C++ hierarchy must
always have the exact same layout as a C struct. Until that happens, I would
advise not writing code like that and I'd advise to always reject such code in
code reviews.
> So why is it required that:
> > struct empty {};
> >
> > struct A : empty
> > {
> > int i;
> > };
> >
> > sizeof(A) == sizeof(int)
> > offsetof(A, i) == 0
>
> The second one is required; the first is not. `A` is not required to be
> layout compatible with `int`.
Why is the second required? Why can't struct A be layout-equivalent to
struct Aprime
{
empty _;
int i;
};
> However, `A` is required to be layout compatible with B, C, and D here:
>
> struct other_empty {};
> struct B : other_empty, A {};
> struct C : A, other_empty {};
> struct my_int { int i; };
> struct D : my_int {};
>
> Now, the sizes of B, C, and D are not required by the standard to be the
> same. But you can stick them in a `union` and fetch the same `::i`
> subobject from any of them. And that's good enough.
Again, why? The only one that seems to me you could put in a union and access
A::i is C, because it derives from A, so by definition it follows the initial
sequence requirement.
struct B has a member before A, even if empty.
struct D has a wholly different hierarchy.
I guess this is the interesting
one, because if we say that the initial sequence only applies to non-static
data members and does not include base sub-objects, then the only member in
both A and D is "int i". But even if that's the case (and I'd need specific
wording in the standard saying it is), it doesn't mean the bases must be
empty.
As you said yourself:
> You are correct that the standard does not require that these objects are
> the same size as one another. That an implementation can assign space for
> these base classes at the end of the type.
Then they are not empty, are they?
struct empty {};
struct H { int i; };
struct J : empty { int i; };
struct A
{
H h;
int a;
};
struct B
{
J j;
int b;
};On 6 December 2017 at 14:39, Nicol Bolas <jmck...@gmail.com> wrote:It's not fixing the problem it claims to because it lacks the guarantees that standard layout provides with EBO.
So I make the claim that without some kind of guarantee of optimization, this feature fails to achieve its goals.I disagree. Its goal is to provide a superior alternative to EBO, and in my view, it achieves that.
An implementation is (as ever) permitted to use stupid layout for structs -- inserting arbitrarily large amounts of padding for no reason is a valid implementation technique, and remains so in the presence of this feature. You're right that standard-layout structs do give an EBO guarantee in some cases, thereby disallowing lead padding, but an implementation can still pad out the *end* of the class with space for the empty base class if it wants (and indeed implementations actually do that in some cases).
Also, in generic code, you often cannot ensure that the class for which you would like to perform EBO is a standard-layout class, so special guarantees that apply only for standard-layout structs are often irrelevant.
However, I think it would be reasonable to provide stronger layout guarantees for standard layout types when this attribute is used; the feature has not yet been discussed in CWG, and this seems like a reasonable thing to consider at that point. It's certainly in line with the design intent to provide the same guarantees for fields with this attribute as for base classes in the standard-layout case.
So, what would it take to fix this? Well, one big annoyance with P0840 is that it is defined as an attribute. And it stakes out its position on the validity of defining such a thing the idea that, so long as the code functions the same whether the attribute is implemented or not, then it's fine to use an attribute.
Following that rule (which, let's be frank, only exists as an expedient means of getting the proposal through the committee, not because it makes the feature better in some way)This parenthetical is incorrect. Modeling this feature as an attribute makes it a conforming extension in prior language modes.
We did consider in committee the possibility of a keyword instead of an attribute, but as you can tell from the progress of the paper, the attribute approach won the day.makes it impossible to ensure layout compatibility to between `S<T>` and `U<T>`. But that doesn't mean we cannot have some reasonable guarantees on whether `U<T>::t` is zero-sized.
When I was thinking about my own zero-sized idea, discussed here, I came upon the whole "unique identity rule" issue. Namely, that every object of the same type T must have a unique address. None of my solutions to that issue ever really pleased me. This proposal kind of sidesteps that problem, since the attribute is effectively saying "this subobject does not have to be uniquely identified, so it may be zero sized", rather than my approach of "this subobject is zero sized and therefore may not be uniquely identified".
Of course, that's part of the problem: users don't actually care about unique identity; they care about the size.
I brought up my idea because, while investigating it, I happened upon a solution that seems to dovetail very closely with this proposal. But first, let's look at the scope of the problem. Let's say you have this type:struct empty {};
struct A
{
[[no_unqiue_address]] empty x;
empty y;
};
While `x` does not have to have a unique address, `y` does. And therefore `y`'s address must be unique from `x`'s. And since we're not allowed to reorder members, the compiler must include space for `x`.Minor correction: we actually are allowed to reorder [[no_unique_address]] members.
sizeof(A) must still be at least 2, because the two 'empty' members must have distinct addresses, but the 'x' member can be in A's tail padding and thus reusable. However, it's desirable for an initial [[no_unique_address]] member to result in the same layout as a base class, so that there is no ABI change in moving from the old solution to the new one. In practice, that means the above struct will likely be laid out with the 'x' member at offset 0 and the 'y' member at offset 1.
class [[no_unique_address]] empty_t {};
// if you prefer keyword instead of attribute:
inline class empty_t {};
class Foo {
empty_t a, b;
int c;
};
class Bar {
int i;
};
In that case, Foo would have the same layout as Bar.
This solve the problem of empty classes, but not the case of members with padding.
So one could extend the meaning of this attribute (or the inline class if you prefer):
The padding of an object can overlap other objects wherever they are.
Let's formalize that a bit. (I use the inline keyword to simplify my explanation, but anything could be used here)
An inline class is a class whose tail padding can be safely overlapped with any
objects. If the inline class happens to be empty, then its padding can
also be used by another instance of the same class, effectively sharing
their address.
An inline class would then have two sizes: one size
considering tail padding (regular sizeof), and one ignoring tail
padding: inline sizeof().
Internal padding is kept as usual.
//examples
// Basic inline class
inline class A {
double d;
float f;
};
static_assert(sizeof(A) == 16, "");
static_assert(inline sizeof(A) == 12, "");
static_assert(alignof(A) == 8, "");
// Class with an inline class member
class B {
A a;
float f;
};
static_assert(sizeof(B) == 16, "");
static_assert(inline sizeof(B) == 16, "");
static_assert(alignof(B) == 8, "");
// Basic class (not same layout as the inline equivalent)
class C {
double d;
float f;
};
static_assert(sizeof(C) == 16, "");
static_assert(inline sizeof(C) == 16, "");
static_assert(alignof(C) == 8, "");
// Class using a regular class member
class D {
C c;
float f;
};
static_assert(sizeof(D) == 24, "");
static_assert(inline sizeof(D) == 24, "");
static_assert(alignof(D) == 8, "");
// Inline class with a regular class member
inline class E {
C c;
float f;
};
static_assert(sizeof(E) == 24, "");
static_assert(inline sizeof(E) == 20, "");
static_assert(alignof(E) == 8, "");
The inline size is not necessarily a multiple of the alignment and can be any integer >= 0.
I hope these examples are explicit enough to show my idea.
I
would say that all those classes (both regular and inline) could be
standard-layout, but could be difficult to specify this concept with
padding removal (how to define the C equivalent?).
One way would be
to flatten the member hierarchy whenever a member is an inline class
(which would be coherent with the "inline" keyword).
One should
also consider: shall we make "inline" classes opt-in or opt-out. The
simplest would be opt-in, but it would be more useful to have it
opt-out: I think most of the classes throughout the world just don't
mind.
What do you think?
On Thursday, 7 December 2017 19:00:53 PST Nicol Bolas wrote:
> "Often"? I think you overestimate how many non-standard-layout types are
> running around.
In my experience, the vast majority of types in C++ are not standard-layout.
That said, this population may not be relevant. We need to know how many types
that would be used with [[no_unique_address]] are standard-layout, non-final,
don't have unnecessary "using" clauses, and don't need to be used multiple
types by the same container.
> ... that's very odd. I'm somewhat torn on how useful that kind of forward
> compatibility will be. After all, if this space optimization were that
> crucial to me, wouldn't I want to use the mechanism that the version of the
> language my code is written against requires, rather than the one that may
> well be ignored?
I think the keyword here is "crucial". It's not *that* crucial in most cases.
it's just a nice thing to have, which is probably why we still refer to it as
an optimisation.
I personally would use the attribute knowing that the good compilers have
implemented the attribute, and let my users know to upgrade away from those
that don't. I do this all the time: I feel no problem in providing a worse
experience for compilers that provide worse C++ support and that fail to keep
up with the times, especially when they fail to keep up with free and open
source ones.
On Thursday, December 7, 2017 at 5:15:35 PM UTC-5, Richard Smith wrote:On 6 December 2017 at 14:39, Nicol Bolas <jmck...@gmail.com> wrote:It's not fixing the problem it claims to because it lacks the guarantees that standard layout provides with EBO.
So I make the claim that without some kind of guarantee of optimization, this feature fails to achieve its goals.I disagree. Its goal is to provide a superior alternative to EBO, and in my view, it achieves that.But it is not a strict superset of EBO. That is, it has advantages in usability, but you pay for that by not knowing if it will actually be optimized away.If the extent of template metaprogramming has taught me anything, it is that C++ programmers have shown a willingness to suffer through unimaginable pain to gain an optimization.An implementation is (as ever) permitted to use stupid layout for structs -- inserting arbitrarily large amounts of padding for no reason is a valid implementation technique, and remains so in the presence of this feature. You're right that standard-layout structs do give an EBO guarantee in some cases, thereby disallowing lead padding, but an implementation can still pad out the *end* of the class with space for the empty base class if it wants (and indeed implementations actually do that in some cases).Actually they can't; I explained how the standard forbids it at the bottom of my reply to Thaigo. And those implementations that add that padding at the end are defective.
Also, in generic code, you often cannot ensure that the class for which you would like to perform EBO is a standard-layout class, so special guarantees that apply only for standard-layout structs are often irrelevant."Often"? I think you overestimate how many non-standard-layout types are running around.However, I think it would be reasonable to provide stronger layout guarantees for standard layout types when this attribute is used; the feature has not yet been discussed in CWG, and this seems like a reasonable thing to consider at that point. It's certainly in line with the design intent to provide the same guarantees for fields with this attribute as for base classes in the standard-layout case.That's basically my whole point: that this feature ought to provide the same guarantees as standard layout base classes. Or rather, if it doesn't provide the same guarantee, then you give people incentive to keep using base classes despite the potential problems.
So, what would it take to fix this? Well, one big annoyance with P0840 is that it is defined as an attribute. And it stakes out its position on the validity of defining such a thing the idea that, so long as the code functions the same whether the attribute is implemented or not, then it's fine to use an attribute.
Following that rule (which, let's be frank, only exists as an expedient means of getting the proposal through the committee, not because it makes the feature better in some way)This parenthetical is incorrect. Modeling this feature as an attribute makes it a conforming extension in prior language modes.... that's very odd. I'm somewhat torn on how useful that kind of forward compatibility will be. After all, if this space optimization were that crucial to me, wouldn't I want to use the mechanism that the version of the language my code is written against requires, rather than the one that may well be ignored?
We did consider in committee the possibility of a keyword instead of an attribute, but as you can tell from the progress of the paper, the attribute approach won the day.makes it impossible to ensure layout compatibility to between `S<T>` and `U<T>`. But that doesn't mean we cannot have some reasonable guarantees on whether `U<T>::t` is zero-sized.
When I was thinking about my own zero-sized idea, discussed here, I came upon the whole "unique identity rule" issue. Namely, that every object of the same type T must have a unique address. None of my solutions to that issue ever really pleased me. This proposal kind of sidesteps that problem, since the attribute is effectively saying "this subobject does not have to be uniquely identified, so it may be zero sized", rather than my approach of "this subobject is zero sized and therefore may not be uniquely identified".
Of course, that's part of the problem: users don't actually care about unique identity; they care about the size.
I brought up my idea because, while investigating it, I happened upon a solution that seems to dovetail very closely with this proposal. But first, let's look at the scope of the problem. Let's say you have this type:struct empty {};
struct A
{
[[no_unqiue_address]] empty x;
empty y;
};
While `x` does not have to have a unique address, `y` does. And therefore `y`'s address must be unique from `x`'s. And since we're not allowed to reorder members, the compiler must include space for `x`.Minor correction: we actually are allowed to reorder [[no_unique_address]] members.I see nothing in P0840R1 that changes [class.mem]/17, which is what specifies the order that members are allocated in. Without such a change, reordering is not permitted.
sizeof(A) must still be at least 2, because the two 'empty' members must have distinct addresses, but the 'x' member can be in A's tail padding and thus reusable. However, it's desirable for an initial [[no_unique_address]] member to result in the same layout as a base class, so that there is no ABI change in moving from the old solution to the new one. In practice, that means the above struct will likely be laid out with the 'x' member at offset 0 and the 'y' member at offset 1.I agree that this would be ideal. However, the rules P0840R1 specifies don't agree. In addition to the lack of changes [class.mem]/17, P0840 makes changes to [class.mem]/21, which governs layout compatibility. There, `no_unique_address`-qualified members are not ignored; they are explicitly required to be defined in both types for them to be layout compatible.
Naturally, if the committee likes the idea of enforced `no_unique_address` optimization for standard layout types, then I would say that the standard should change the layout compatibility rules to take advantage of it. However, the problem there is that P0840 is very clearly designed to avoid doing things such that removing the `no_unique_address` attribute would change the visible behavior of the program. As it currently stands, two types that are layout compatible with the attribute are still layout compatible without it.If we do what I just suggested, this would no longer be the case.Now of course, the ABI is not defined by layout compatibility; two incompatible types may use the same ABI definition. But C++ doesn't enforce it, so you wouldn't be able to put such types in unions and access each others' subobjects.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/3d091ab0-977c-4d24-a3d2-4b6b68f68c57%40isocpp.org.
On Thursday, 7 December 2017 18:31:13 PST Nicol Bolas wrote:
> > blame
> > Microsoft for not adding __has_include until VS 15.3 (there was a thread
> > even
> > in std-discussions about that).
>
> ... that's not even the same kind of thing. `__has_include` is not an
> optional feature of C++17. It was going to happen in VS, sooner or later.
But was optional in C++11 and 14.
> [class.mem]/14 confounds you:
> > Non-static data members of a (non-union) class with the same access
>
> control (Clause 14) are allocated so
> that later members have higher addresses within a class object.
>
> And you cannot violate that requirement, since `no_unique_address` is an
> attribute.
And probably another reason why it shouldn't be an attribute.
> And the reasoning P0840 gave for allowing it to be an attribute
> rather than a keyword is that removing it means that the program still
> functions the "same", without affecting visible behavior (again, this is a
> lie, but whatever). Reordering `no_unique_address` members would be
> observable defined behavior; it would affect the result of `offsetof`, for
> example.
And it doesn't now? The fact that there are two members with the same address,
even if different types, is observable behaviour. And as I pointed out in the
previous reply, a struct with no_unique_address members should not be standard
layout, so another observable behaviour.
> > By the way, note how [[no_unique_address]] implies non-standard layout.
>
> It implies no such thing. Indeed, it changes *nothing* about the standard
> layout rules; the specification is very clear on that.
The specification can claim all it wants but it doesn't make it so.
If a member has no unique address, how can this be of standard-layout? Such a
thing cannot exist in C, so it's not possible to follow the layout rules for C
in the first place since there are no rules for this any more than there are
for virtual tables.
On 7 December 2017 at 19:00, Nicol Bolas <jmck...@gmail.com> wrote:On Thursday, December 7, 2017 at 5:15:35 PM UTC-5, Richard Smith wrote:On 6 December 2017 at 14:39, Nicol Bolas <jmck...@gmail.com> wrote:It's not fixing the problem it claims to because it lacks the guarantees that standard layout provides with EBO.
So I make the claim that without some kind of guarantee of optimization, this feature fails to achieve its goals.I disagree. Its goal is to provide a superior alternative to EBO, and in my view, it achieves that.But it is not a strict superset of EBO. That is, it has advantages in usability, but you pay for that by not knowing if it will actually be optimized away.If the extent of template metaprogramming has taught me anything, it is that C++ programmers have shown a willingness to suffer through unimaginable pain to gain an optimization.An implementation is (as ever) permitted to use stupid layout for structs -- inserting arbitrarily large amounts of padding for no reason is a valid implementation technique, and remains so in the presence of this feature. You're right that standard-layout structs do give an EBO guarantee in some cases, thereby disallowing lead padding, but an implementation can still pad out the *end* of the class with space for the empty base class if it wants (and indeed implementations actually do that in some cases).Actually they can't; I explained how the standard forbids it at the bottom of my reply to Thaigo. And those implementations that add that padding at the end are defective.Interesting, this is a defect in the standard. Consider:struct A {};struct B : A { int n; };struct X { B b; int m; };struct alignas(64) C {};struct D : C { int n; };struct Y { D d; int m; };The offset of m in X and the offset of m in Y are different, because D has tail padding for alignment and B does not.
Also, in generic code, you often cannot ensure that the class for which you would like to perform EBO is a standard-layout class, so special guarantees that apply only for standard-layout structs are often irrelevant."Often"? I think you overestimate how many non-standard-layout types are running around.However, I think it would be reasonable to provide stronger layout guarantees for standard layout types when this attribute is used; the feature has not yet been discussed in CWG, and this seems like a reasonable thing to consider at that point. It's certainly in line with the design intent to provide the same guarantees for fields with this attribute as for base classes in the standard-layout case.That's basically my whole point: that this feature ought to provide the same guarantees as standard layout base classes. Or rather, if it doesn't provide the same guarantee, then you give people incentive to keep using base classes despite the potential problems.Only if their implementation produces unnecessarily-wasteful class layouts. But the standard doesn't dictate that. If you're relying on particular layouts, you're relying on implementation-specific behavior already. Users have no incentives to use EBO if the alternative is at least as good in every case on every real implementation.Is it accurate to summarize your concern as: some implementation might choose to produce a wasteful class layout, and you as a user would then lack the recourse of telling them they're non-conforming?
So, what would it take to fix this? Well, one big annoyance with P0840 is that it is defined as an attribute. And it stakes out its position on the validity of defining such a thing the idea that, so long as the code functions the same whether the attribute is implemented or not, then it's fine to use an attribute.
Following that rule (which, let's be frank, only exists as an expedient means of getting the proposal through the committee, not because it makes the feature better in some way)This parenthetical is incorrect. Modeling this feature as an attribute makes it a conforming extension in prior language modes.... that's very odd. I'm somewhat torn on how useful that kind of forward compatibility will be. After all, if this space optimization were that crucial to me, wouldn't I want to use the mechanism that the version of the language my code is written against requires, rather than the one that may well be ignored?You presumably would not use this feature if your implementation doesn't support it. But, for instance, it sounds like the maintainer of libstdc++ is interested in using it whenever it's available (even in older language standards), to present the name leakage problems introduced by EBO, and falling back to EBO when the feature is not available. Other authors of portable libraries will likely do the same.
sizeof(A) must still be at least 2, because the two 'empty' members must have distinct addresses, but the 'x' member can be in A's tail padding and thus reusable. However, it's desirable for an initial [[no_unique_address]] member to result in the same layout as a base class, so that there is no ABI change in moving from the old solution to the new one. In practice, that means the above struct will likely be laid out with the 'x' member at offset 0 and the 'y' member at offset 1.I agree that this would be ideal. However, the rules P0840R1 specifies don't agree. In addition to the lack of changes [class.mem]/17, P0840 makes changes to [class.mem]/21, which governs layout compatibility. There, `no_unique_address`-qualified members are not ignored; they are explicitly required to be defined in both types for them to be layout compatible.I think you're missing the point. The intent is to *allow* there to be no ABI change, not to *require* there to be no ABI change.
struct A
{
int i;
};
struct B
{
[[no_unique_address]] empty e;
int j;
};To me the issue is the idea that the compiler can ignore all an every attributes.I'd argue that attributes should be allowed to change the semantic of a program, on a per-attribute basis.[[no_unique_address]] is a perfect exemple. It is arcane enough that it does not warrant a new keyword or the re-purposing of one ( like register ), and act like a kind of qualifier. For the reason you pointed out, there is an expectation that this attributes has an unconditional semantic significance and enforcing that in the program seems reasonable.I'm not, by any means, suggesting that all futures keywords be implemented in terms of attributes. The decision of introducing attributes that are not discard-able should be made on a per-attributes basis.I'm working on a set of rules
- Exisiting standard attributes are ignorable
- Compilers / tooling provided attributes are always ignorable
- The committee may decide in the future to introduce attributes that are not ignorable and carry semantic meaning. Compilers that ignore those attributes are not-conformant. Adding or removing such attributes may change the meaning if the program.
- Whether futures attributes are ignorable will be decided on a per-attribute basis;
That would let the committee decide what the best semantic for [[no_unique_address]] is.And we can build upon that to introduce user-defined attributes and attribute reflection;The idea being that user-defined attributes are always namespaced so that the standard can keep adding new attributes without running into name-collision.
6 For an attribute-token (including an attribute-scoped-token) not specified in this document, the behavior is
implementation-defined. Any attribute-token that is not recognized by the implementation is ignored. [ Note:
Each implementation should choose a distinctive name for the attribute-namespace in an attribute-scoped-token.
—end note ]
You won't find it in the standard. It's a philosophy of what the committee decides to make attributes vs. real syntax. The dividing line is generally this: if you remove the attribute, will the code that compiled with those attributes still compile and have the same meaning? If the answer is "no", then it shouldn't be an attribute.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/898d6906-82b3-488a-930d-6259f0960a03%40isocpp.org.
Hello everybody,
I would want to give my point on view on this subject, I have to want you, I'm not a standard expert, just a user.
I agree with Nicol that the information about address uniqueness should embedded in the type itself, and not in its uses.
And my question was even more restrictive: how many of those classes are
expected to be used with [[no_unique_address]]?
struct mine
{
a_type val;
[[no_unique_address]] some_type t;
};struct mine_old : some_type
{
std::vector<int> ivec;
};Looking at the entirety of C++
class population, those that are standard-layout, or even those that are
intentionally standard-layout is still biasing the statistic.
> So you're telling me that we call it an optimization, not because that's
> what we called it before it was required, but because it's just a nice
> thing to have?
Yeah, I was.
> Sorry, not buying it. Etymology and word usage just doesn't work that way.
> People call things what they're used to calling them. RVO is still called
> an "optimization" even though it's required in many cases now. And people
> will continue to call it that. Not because it's just "a nice thing to have"
> but because *that's its name*.
As a counter-example, we have "mandatory ellision" now, which technically RVO
is a type of. In an ideal world, we'd refer by the correct term.
But people still call the Standard Library the "STL" even though it isn't
called that and there are many classes that aren't templates. So, yes, old
habits die hard.
> > I personally would use the attribute knowing that the good compilers have
> > implemented the attribute, and let my users know to upgrade away from
> > those
> > that don't. I do this all the time: I feel no problem in providing a worse
> > experience for compilers that provide worse C++ support and that fail to
> > keep
> > up with the times, especially when they fail to keep up with free and open
> > source ones.
>
> That's fine; you're in a position where that's a reasonable choice to make.
> Other people aren't. Why *shouldn't* we help them switch to this
> functionality? Because that's what we're talking about.
We should help them switch, but I don't think we have to get our of our way to
do so. If an attribute suffices for the functionality and most compilers
implement it (or will soon), that should be it. There's no need to create a
keyword, mostly because it doesn't help much with adoption since people will
have to check #ifdef __cpp_no_unique_address and define their macro anyway.
In any case, I do think we need a keyword but for other reasons.
> That's what I'm arguing for. You may not think it makes sense that standard
> layout types guarantee EBO. But given the fact that this guarantee exists,
> why does it not make sense for `no_unique_address` to provide the same
> guarantee?
It's a side-effect guarantee, not an intentional part.