P0329R0 falls short for designated initialisers

381 مرّة مشاهدة
التخطي إلى أول رسالة غير مقروءة

Thiago Macieira

غير مقروءة،
26‏/07‏/2017، 12:25:03 ص26‏/7‏/2017
إلى std-pr...@isocpp.org
There are two extra requirements for the C++ version of the feature that don't
exist in C99:
1) initialisers must appear in the order of declaration
2) nesting not permitted.

That means you cannot write:

time_t now = time(nullptr);
struct stat stbuf = {
.st_dev = 0,
.st_ino = 0,
.st_mode = S_IFREG | 0644,
.st_nlink = 1,
.st_uid = 0,
.st_gid = 0,
.st_rdev = 0,
.st_size = 123,
.st_atime = now,
.st_mtime = now,
.st_ctime = now,
.st_blocksize = 512,
.st_blocks = 1
};

Even though I followed the POSIX documentation
http://pubs.opengroup.org/onlinepubs/009695399/basedefs/sys/stat.h.html

The above has both problems:
1) the order of the fields is not the same in all OSes, not even in the
multiple architectures of a given OS. On Linux, for example, the order of
st_mode and st_nlink is reversed on x86 32-bit and both st_blocksize and
st_blocks occur before the time fields

2) the three time fields are actually macros:
# define st_atime st_atim.tv_sec /* Backward compatibility. */
# define st_mtime st_mtim.tv_sec
# define st_ctime st_ctim.tv_sec

The code above works fine for C99 but not C++.

That means designated initialisers cannot be used on structures you don't
control, since the order may vary from implementation to implementation. And
this trick of #defining the name of a member to point to a sub-field is actually
very common, sometimes taken to an extreme, like in siginfo_t.

So, to keep compatibility with C99, can we relax both rules for (at least)
trivial types?

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

d25f...@outlook.com

غير مقروءة،
26‏/07‏/2017، 3:39:59 ص26‏/7‏/2017
إلى std-pr...@isocpp.org،Thiago Macieira
Given that `.d { .d2 = 2 },` is allowed in the proposal (p5), I don't
see the reason why `.e.a = 2,` is forbidden (p2).

But except for the case here, nesting designators seems to make code
hard to read. (But this is a coding convention matter.)

>
> That means designated initialisers cannot be used on structures you don't
> control, since the order may vary from implementation to implementation. And
> this trick of #defining the name of a member to point to a sub-field is actually
> very common, sometimes taken to an extreme, like in siginfo_t.
>
> So, to keep compatibility with C99, can we relax both rules for (at least)
> trivial types?
>
Not for the compatibility with C99, but I still agree that the rule #1
should be relaxed.

For trivial types it should be straightforward.

I also think we may further allow the initialization order to be
inconsistent with declaration order of the data members (while keep the
evaluation order from left to right), as it's already allowed today in
constructor' member initializer list (*), and thus relax rule #1 for
non-trivial types.

We might also introduce new attribute `[[unordered_init]]` or something
alike to suppress warnings the compiler issues when initialization order
is not consistent with the declaration order.

---

*: But still there is a difference that when writing constructor's
member initializer list, the definition of the class in question is
controlled by us, while when using designator initializers the structure
to be initialized may be defined by third parties.

Zhihao Yuan

غير مقروءة،
26‏/07‏/2017، 3:51:03 ص26‏/7‏/2017
إلى std-pr...@isocpp.org
On Tue, Jul 25, 2017 at 11:24 PM, Thiago Macieira <thi...@macieira.org> wrote:
> So, to keep compatibility with C99, can we relax both rules for (at least)
> trivial types?
>

The authors considered this option, but do not
feel this is a viable choice.

First, the motivation seems not strong enough to
us. If the struct itself is already trivial, using the
traditional value-init + assignment approach

stat buf{};
buf.atime = now;
...

can expect a codegen that is identical to using
designated initializers. At least that's what gcc
does; if you found that your compiler produces
different code and one of which is suboptimal,
you should file a bug to the vendor.

Second, relaxing these rules to trivial types can
bring some negative consequences. Consider
that you shipped a library with an option struct,
initially a trivial type, an innocent user in his/her
code initialized an object of such a type using
unordered designators, in next version of the
library you appended a `std::string` field to that
struct, then the user's code breaks. Designated
initializers intend to establish a way to reduce
such breakage caused by library evolving, but
relaxing the rules to trivial types can defeat such
a purpose.

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________

Zhihao Yuan

غير مقروءة،
26‏/07‏/2017، 4:12:26 ص26‏/7‏/2017
إلى std-pr...@isocpp.org،Thiago Macieira
On Wed, Jul 26, 2017 at 2:39 AM, d25f...@outlook.com
<d25f...@outlook.com> wrote:
> For trivial types it should be straightforward.
>

No, explained in the previous email. It
makes the language harder to use.

> I also think we may further allow the initialization order to be
> inconsistent with declaration order of the data members (while keep the
> evaluation order from left to right), as it's already allowed today in
> constructor' member initializer list (*), and thus relax rule #1 for
> non-trivial types.

First, mem-init-clauses are evaluated in the
member declaration orders rather than the
written order. It's a design mistake and let's
not repeat that. The approach you implied,
thus, allowing the evaluation order diverges
from the initialization order, will make this
feature not pay-for-what-you-use. These
are already explained in the paper.

d25f...@outlook.com

غير مقروءة،
26‏/07‏/2017، 6:18:56 ص26‏/7‏/2017
إلى std-pr...@isocpp.org،Zhihao Yuan،Thiago Macieira
On 2017/7/26 16:12, Zhihao Yuan wrote:
> On Wed, Jul 26, 2017 at 2:39 AM, d25f...@outlook.com
> <d25f...@outlook.com> wrote:
>> For trivial types it should be straightforward.
>>
>
> No, explained in the previous email. It
> makes the language harder to use.
>
>> I also think we may further allow the initialization order to be
>> inconsistent with declaration order of the data members (while keep the
>> evaluation order from left to right), as it's already allowed today in
>> constructor' member initializer list (*), and thus relax rule #1 for
>> non-trivial types.
>
> First, mem-init-clauses are evaluated in the
> member declaration orders rather than the
> written order. It's a design mistake and let's
> not repeat that. The approach you implied,
> thus, allowing the evaluation order diverges
> from the initialization order, will make this
> feature not pay-for-what-you-use. These
> are already explained in the paper.
>

I think I expressed my thoughts wrong.

By "initialization order", I was trying to mean "the order the
designated initializers are listed", not "the order the class' member
variables are initialized". Sorry for the misleading term.

What I tried to mean is that we can "repeat the mistake" and initialize
the object using the order its members are declared, regardless the
order the designated initializers are listed, and leaves the compiler to
warn the user if appropriate. We can also allow the user to suppress the
warning using some attribute on his will.

Still, even if it's a mistake for mem-init-clause, for the third party
structures whose definition cannot be controlled by us, the "severity of
the mistake" is mitigated. Personally I do not take it as a "mistake" as
there's otherwise no easy way to initialize those "weird" third party
structures (*) other than falls back to what we have today.

Also, in this way, there should be no temporary involved.
("pay-for-what-you-use")

---

*: Those whose definitions are changing from OS to OS, from ISA to ISA
(as in Thiago's post), or not declared in a logical order (presumbly due
to backward compatibility -- New fields can only be added at the end of
the structure).

Thiago Macieira

غير مقروءة،
26‏/07‏/2017، 11:53:41 ص26‏/7‏/2017
إلى std-pr...@isocpp.org
On quarta-feira, 26 de julho de 2017 00:50:59 PDT Zhihao Yuan wrote:
> First, the motivation seems not strong enough to
> us. If the struct itself is already trivial, using the
> traditional value-init + assignment approach
>
> stat buf{};
> buf.atime = now;
> ...

It's not equivalent if I'm trying to have a static const variable. Taking an
example from Linux kernel's source code:

static const struct xattr_handler shmem_security_xattr_handler = {
.prefix = XATTR_SECURITY_PREFIX,
.get = shmem_xattr_handler_get,
.set = shmem_xattr_handler_set,
};

A lot of this is done for virtual tables, but we are faced with this in C++
code as well when interfacing with C libraries. Examples from Qt source code:

http://code.qt.io/cgit/qt/qtwayland.git/tree/src/client/
qwaylanddisplay.cpp#n349
http://code.qt.io/cgit/qt/qtwayland.git/tree/src/hardwareintegration/client/
brcm-egl/qwaylandbrcmeglwindow.cpp#n83
http://code.qt.io/cgit/qt/qtwayland.git/tree/tests/auto/client/client/
mockcompositor.cpp#n321
http://code.qt.io/cgit/qt/qtwayland.git/tree/tests/auto/client/client/
mockshell.cpp#n149
http://code.qt.io/cgit/qt/qtwayland.git/tree/tests/auto/client/client/
mockshell.cpp#n171

We are forced to use non-designated initialisation, which means we could end
up with:

static const struct operations ops = {
nullptr,
nullptr,
nullptr,
nullptr,
do_open,
nullptr,
nullptr,
noop,
nullptr,
do_close
};

Matt Calabrese

غير مقروءة،
26‏/07‏/2017، 12:19:56 م26‏/7‏/2017
إلى ISO C++ Standard - Future Proposals
On Wed, Jul 26, 2017 at 11:53 AM, Thiago Macieira <thi...@macieira.org> wrote:
On quarta-feira, 26 de julho de 2017 00:50:59 PDT Zhihao Yuan wrote:
> First, the motivation seems not strong enough to
> us.  If the struct itself is already trivial, using the
> traditional value-init + assignment approach
>
>   stat buf{};
>   buf.atime = now;
>   ...

It's not equivalent if I'm trying to have a static const variable.

IIUC, you can still get constant initialization and also avoid dependence on member order even in that case. It doesn't require designated initializers:


-- Matt Calabrese

Zhihao Yuan

غير مقروءة،
26‏/07‏/2017، 1:10:26 م26‏/7‏/2017
إلى d25f...@outlook.com،Thiago Macieira،std-pr...@isocpp.org
On Jul 26, 2017 5:18 AM, "d25f...@outlook.com" <d25f...@outlook.com> wrote:

By "initialization order", I was trying to mean "the order the
designated initializers are listed", not "the order the class' member
variables are initialized". Sorry for the misleading term.

What I tried to mean is that we can "repeat the mistake" and initialize
the object using the order its members are declared, regardless the
order the designated initializers are listed, and leaves the compiler to
warn the user if appropriate. We can also allow the user to suppress the
warning using some attribute on his will.

Still, even if it's a mistake for mem-init-clause, for the third party
structures whose definition cannot be controlled by us, the "severity of
the mistake" is mitigated.

The severity of the mistake is increased.
You essentially get a feature to
encourage silently breaking users'
legitimate code which relies on a
specific evaluation order of the
initializers by changing the member
declaration order at the library side.

--
Zhihao

Nicol Bolas

غير مقروءة،
26‏/07‏/2017، 1:49:20 م26‏/7‏/2017
إلى ISO C++ Standard - Future Proposals،d25f...@outlook.com،thi...@macieira.org،z...@miator.net

I think another way to look at it is this. Designated initializers are just a convenience form of aggregate initialization (one that notably allows you to skip members and let them be initialized via their default member initializers). If you couldn't guarantee the order of the items in the struct, then you couldn't use aggregate initialization to begin with.  And thus, you shouldn't be surprised that you can't use designated initializers.

Initializing members out-of-order through aggregate initialization would be a new thing, not a convenience feature.

Nicol Bolas

غير مقروءة،
26‏/07‏/2017، 1:50:36 م26‏/7‏/2017
إلى ISO C++ Standard - Future Proposals،d25f...@outlook.com،thi...@macieira.org،z...@miator.net

But at least with designated initializers, if you initialize them out-of-order, then at least you get a compile error, so that you can fix your code. With regular aggregate initialization, you wouldn't even get that.

Zhihao Yuan

غير مقروءة،
26‏/07‏/2017، 5:33:16 م26‏/7‏/2017
إلى Nicol Bolas،ISO C++ Standard - Future Proposals،d25f...@outlook.com،Thiago Macieira،Zhihao Yuan
On Wed, Jul 26, 2017 at 12:49 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> Designated initializers are just
> a convenience form of aggregate initialization (one that notably allows you
> to skip members and let them be initialized via their default member
> initializers).

That's exactly how we model this feature
when we were designing it; stated as the 2nd
motivation in the paper.

> Initializing members out-of-order through aggregate initialization would be
> a new thing, not a convenience feature.

Well, we tried very hard on that, though it's not
a part of the motivation, but nothing really works
so far.

Victor Dyachenko

غير مقروءة،
31‏/07‏/2017، 7:28:09 ص31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals
I support Thiago. This valid C99/11 code should also be valid С++:

#include<time.h>

struct timespec1 { time_t tv_sec; long tv_nsec; };
struct timespec2 { long tv_nsec; time_t tv_sec; };
struct timespec3 { time_t tv_sec; long tv_nsec; int extra_field; };

struct timespec1 t1 = { .tv_sec = 1, .tv_nsec = 0 };
struct timespec2 t2 = { .tv_sec = 1, .tv_nsec = 0 };
struct timespec3 t3 = { .tv_sec = 1, .tv_nsec = 0 };

Thiago Macieira

غير مقروءة،
31‏/07‏/2017، 12:41:13 م31‏/7‏/2017
إلى std-pr...@isocpp.org
Something I found over the weekend:

https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attributes.html#Common-Type-Attributes

designated_init

This attribute may only be applied to structure types. It indicates that
any initialization of an object of this type must use designated initializers
rather than positional initializers. The intent of this attribute is to allow
the programmer to indicate that a structure’s layout may change, and that
therefore relying on positional initialization will result in future breakage.

GCC emits warnings based on this attribute by default; use -Wno-
designated-init to suppress them.

So GCC devs have already foreseen the need for types that may change order in
the future.

Nicol Bolas

غير مقروءة،
31‏/07‏/2017، 2:05:10 م31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals

Well, the C++ language doesn't work well with that, since members must be initialized in the order they're presented in the class definition. In C++, changing the order of members is not a backwards-incompatible change, and designated initializers will not change that.

Here's the question I have with list and aggregate initialization. Is this well-defined, currently:

struct Agg
{
 
int x, y;
};

Agg a{5, a.x};

In a braced-init-list, the initializing expressions are evaluated in the order they appear. So it could be that the initialization of `a.x` happens before the expression to start initializing `a.y`. If that is the case, then it's basically impossible to do this out-of-order. Even for trivial types, the initializers could depend on each other in very funky ways.

Personally, I hope that it is not well-defined.


Thiago Macieira

غير مقروءة،
31‏/07‏/2017، 2:24:46 م31‏/7‏/2017
إلى std-pr...@isocpp.org
On segunda-feira, 31 de julho de 2017 11:05:09 PDT Nicol Bolas wrote:
> Well, the C++ language doesn't work well with that, since members must be
> initialized in the order they're presented in the class definition. In C++,
> changing the order of members is not a backwards-incompatible change, and
> designated initializers will not change that.

Only because we say it is. There's nothing else preventing initialisation in
any order for trivial types.

Nicol Bolas

غير مقروءة،
31‏/07‏/2017، 2:34:04 م31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals
On Monday, July 31, 2017 at 2:24:46 PM UTC-4, Thiago Macieira wrote:
On segunda-feira, 31 de julho de 2017 11:05:09 PDT Nicol Bolas wrote:
> Well, the C++ language doesn't work well with that, since members must be
> initialized in the order they're presented in the class definition. In C++,
> changing the order of members is not a backwards-incompatible change, and
> designated initializers will not change that.

Only because we say it is. There's nothing else preventing initialisation in
any order for trivial types.

Well, there's the question I just posted. Do you know if that code is legal at present? If it is legal, then that would most assuredly prevent arbitrary initialization ordering for members, even for trivial types.

Thiago Macieira

غير مقروءة،
31‏/07‏/2017، 3:42:13 م31‏/7‏/2017
إلى std-pr...@isocpp.org
On segunda-feira, 31 de julho de 2017 11:34:04 PDT Nicol Bolas wrote:
> Well, there's the question I just posted. Do you know if that code is legal
> at present? If it is legal, then that would most assuredly prevent
> arbitrary initialization ordering for members, even for trivial types.

I don't know if it is. It definitely isn't for non-trivial types, but I don't
know if it should be allowed for trivial ones. The question to be answered is
whether the evaluation of a.x happens before the assignment of 5 to it in the
brace initialiser.

If we say that in a trivial type's brace initialisation each type is
initialised left-to-right and evaluation happens independently, then it would
be valid.

I don't think we want to say that. That would be supported by the fact that
P0329 also disallows reusing a member in the expression for initialisation of
another type (which C99 allows and I'm not asking for).

Either way, that doesn't stop us from allowing:

Agg a { .y = 5, .x = 5 };

Barry Revzin

غير مقروءة،
31‏/07‏/2017، 4:36:52 م31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals
Here's the question I have with list and aggregate initialization. Is this well-defined, currently:

struct Agg
{
 
int x, y;
};

Agg a{5, a.x};

In a braced-init-list, the initializing expressions are evaluated in the order they appear. So it could be that the initialization of `a.x` happens before the expression to start initializing `a.y`. If that is the case, then it's basically impossible to do this out-of-order. Even for trivial types, the initializers could depend on each other in very funky ways.

Personally, I hope that it is not well-defined.


I think that's well-formed. Each element's initialization is sequenced before the next element's initialization (http://eel.is/c++draft/dcl.init.aggr#6), so we'd copy-initialize a.x from 5 and then copy-initialize a.y from a.x. 

Nicol Bolas

غير مقروءة،
31‏/07‏/2017، 5:08:02 م31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals

That must be a very recent change, because N4659 doesn't say that. I wonder where that line came from. For the sake of clarity, [dcl.init.aggr]/6 says:

> The initializations of the elements of the aggregate are evaluated in the element order. That is, all value computations and side effects associated with a given element are sequenced before those of any element that follows it in order.


Daniel Krügler

غير مقروءة،
31‏/07‏/2017، 5:23:47 م31‏/7‏/2017
إلى std-pr...@isocpp.org
2017-07-31 23:08 GMT+02:00 Nicol Bolas <jmck...@gmail.com>:
> On Monday, July 31, 2017 at 4:36:52 PM UTC-4, Barry Revzin wrote:
>> I think that's well-formed. Each element's initialization is sequenced
>> before the next element's initialization
>> (http://eel.is/c++draft/dcl.init.aggr#6), so we'd copy-initialize a.x from 5
>> and then copy-initialize a.y from a.x.
>
> That must be a very recent change, because N4659 doesn't say that. I wonder
> where that line came from.

It was part starting from the first p0329 wording,

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0329r2.pdf

and was voted in by acceptance of p0329r4 during the Toronto meeting
(Soon visible in the publication list).

- Daniel

Nicol Bolas

غير مقروءة،
31‏/07‏/2017، 5:30:17 م31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals

Oh, so that's not actually part of C++17. Well, that changes things.

The point of the question was to determine if it was legal for people to write code where the initialization expressions could rely on initialization order. If that wasn't the case, then we could have unordered designated initializers (for trivial types only) by specifying that the expressions are resolved in the order of the braced-init-list, but they initialize the objects in the order they appear in the aggregate. This would allow the "as if" rule to come into play, allowing implementations to do what they like, while still allowing us to know that subobjects are initialized in order.

The goal was to avoid doing what Thaigo's suggestion would require: completely redefining member subobject initialization order rules.

So long as we make it undefined again before C++20 comes out, then we can take the quick route to doing so.

Bengt Gustafsson

غير مقروءة،
31‏/07‏/2017، 7:07:34 م31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals

My suggestion would be that designated initializers can be written in any order, but are executed in the order of the struct declaration. As an extra rule the compiler can (must?) check that members are not refered before they are initialized.

I really dislike the idea of forcing users to write designated initializers in the same order as the struct declaration have them, not only as the library writer may change the order but more importantly that designated initializers are most useful when you have many members and only initialize a few with non-default values and remembering the order in this case is much harder than remembering the names.

Are there other problems with allowing any order of designated initializers besides checking that not yet initialized members are not referred? In my experience initiing one member from another is very rarely useful, so even if reordering the fields of the struct has a potential to cause compiler errors when compiling users of the struct I don't think this would case very many real problems. If this is the concern I would rather forbid refering to other members from initializers if there are any designators in the initializer.

Thiago Macieira

غير مقروءة،
31‏/07‏/2017، 8:23:11 م31‏/7‏/2017
إلى std-pr...@isocpp.org
On segunda-feira, 31 de julho de 2017 16:07:34 PDT Bengt Gustafsson wrote:
> Are there other problems with allowing any order of designated initializers
> besides checking that not yet initialized members are not referred? In my
> experience initiing one member from another is very rarely useful, so even
> if reordering the fields of the struct has a potential to cause compiler
> errors when compiling users of the struct I don't think this would case
> very many real problems. If this is the concern I would rather forbid
> refering to other members from initializers if there are any designators in
> the initializer.

For non-trivial, yes, there are issues. Take this example:

struct Agg
{
SubObj1 a;
SubObj2 b;
};

Agg a = { .b = 1, .a = 2 };

What happens if b's constructor throws?

That's why I am asking for the any order permission to apply only to trivial
types. Which all structs defined in headers meant to be consumed by C are.

Nicol Bolas

غير مقروءة،
31‏/07‏/2017، 8:49:44 م31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals
On Monday, July 31, 2017 at 7:07:34 PM UTC-4, Bengt Gustafsson wrote:

My suggestion would be that designated initializers can be written in any order, but are executed in the order of the struct declaration. As an extra rule the compiler can (must?) check that members are not refered before they are initialized.

I really dislike the idea of forcing users to write designated initializers in the same order as the struct declaration have them, not only as the library writer may change the order but more importantly that designated initializers are most useful when you have many members and only initialize a few with non-default values and remembering the order in this case is much harder than remembering the names.

Are there other problems with allowing any order of designated initializers besides checking that not yet initialized members are not referred?

Yes.

Braced-init-lists have a very simple rule at present: the expressions are executed in the order they appear. This will be true, whether they call a constructor or initialize an aggregate.

What this will do is turn that rule into a much more complex one. To know the order that those expressions will be executed in, you must look at non-local code. And therefore, it becomes very possible to make two expressions in a braced-init-list depend on the order of execution, but to have that order no longer match the visible order of the expressions. Or worse, that your code works fine at one point, but then a change to the order of the members causes a silent breakage. With enforced ordering, it's a noisy breakage.

This is the fundamental reason why allowing member initializers to work this way was a bad idea. This will effectively lead to compilers having to issue warnings about designated initializers being executed out of their apparent order.
 
In my experience initiing one member from another is very rarely useful, so even if reordering the fields of the struct has a potential to cause compiler errors when compiling users of the struct I don't think this would case very many real problems. If this is the concern I would rather forbid refering to other members from initializers if there are any designators in the initializer.

You cannot effectively forbid that sort of thing. You can always have one initializer reference itself by getting a pointer and storing it locally, then have another initializer expression read from that pointer. Or have two functions which have some shared state, such that their order of execution matters. And so forth.

Zhihao Yuan

غير مقروءة،
31‏/07‏/2017، 10:28:55 م31‏/7‏/2017
إلى std-pr...@isocpp.org
On Mon, Jul 31, 2017 at 2:42 PM, Thiago Macieira <thi...@macieira.org> wrote:
> It definitely isn't for non-trivial types, but I don't
> know if it should be allowed for trivial ones.
>
> [...]
>
> Either way, that doesn't stop us from allowing:
>
> Agg a { .y = 5, .x = 5 };
>

I've stated my concern with dividing the feature
into trivial types only and those not -- it brings
damage when the library evolves.

However, I feel it's more acceptable if we allocate
a syntax to let people opt-in. I mean, what if
such a discrimination happens only when a
user asks for it by using the = { ... } initializer
(copy-init syntax)? Examples:

Agg a = { .y = 5, .x = 5 }; // ok
Agg b { .y = 5, .x = 5 }; // no
struct X { Agg a; };
X c { { .a{ .y = 5, .x = 5 } }; // no
X c { { .a = { .y = 5, .x = 5 } }; // ok

Nicol Bolas

غير مقروءة،
31‏/07‏/2017، 11:13:09 م31‏/7‏/2017
إلى ISO C++ Standard - Future Proposals،z...@miator.net
On Monday, July 31, 2017 at 10:28:55 PM UTC-4, Zhihao Yuan wrote:
On Mon, Jul 31, 2017 at 2:42 PM, Thiago Macieira <thi...@macieira.org> wrote:
> It definitely isn't for non-trivial types, but I don't
> know if it should be allowed for trivial ones.
>
> [...]
>
> Either way, that doesn't stop us from allowing:
>
>         Agg a { .y = 5, .x = 5 };
>

I've stated my concern with dividing the feature
into trivial types only and those not -- it brings
damage when the library evolves.

However, I feel it's more acceptable if we allocate
a syntax to let people opt-in.  I mean, what if
such a discrimination happens only when a
user asks for it by using the = { ... } initializer
(copy-init syntax)?

Right, because the thing we most need to do is make "uniform initialization" less uniform.

No, having copy-list-initialization work (almost) exactly like direct-list-initialization is one of the best aspects of list-initialization. We should not undo that lightly.

If unordered designated initializers are dangerous enough to need their own special syntax, then they're too dangerous for C++.

Thiago Macieira

غير مقروءة،
01‏/08‏/2017، 1:16:01 ص1‏/8‏/2017
إلى std-pr...@isocpp.org
On segunda-feira, 31 de julho de 2017 19:28:51 PDT Zhihao Yuan wrote:
> > Either way, that doesn't stop us from allowing:
> > Agg a { .y = 5, .x = 5 };
>
> I've stated my concern with dividing the feature
> into trivial types only and those not -- it brings
> damage when the library evolves.

Understood, but it cannot happen for C structures. C developers are the ones
who write structures without constructors and are used to designated
initialisers. It's their structures we have to worry about changing layout.

C++ developers usually add constructors.

> However, I feel it's more acceptable if we allocate
> a syntax to let people opt-in. I mean, what if
> such a discrimination happens only when a
> user asks for it by using the = { ... } initializer
> (copy-init syntax)? Examples:
>
> Agg a = { .y = 5, .x = 5 }; // ok
> Agg b { .y = 5, .x = 5 }; // no
> struct X { Agg a; };
> X c { { .a{ .y = 5, .x = 5 } }; // no
> X c { { .a = { .y = 5, .x = 5 } }; // ok

How about:

X = { .a.x = 5, .a.y = 5 }; // ok or not?

Zhihao Yuan

غير مقروءة،
01‏/08‏/2017، 1:36:11 ص1‏/8‏/2017
إلى std-pr...@isocpp.org
On Tue, Aug 1, 2017 at 12:15 AM, Thiago Macieira <thi...@macieira.org> wrote:
>
> C++ developers usually add constructors.
>

That will be too ideal. There is a difference
between you break intentionally, and you
break without knowing...

>> struct X { Agg a; };
>> X c { { .a{ .y = 5, .x = 5 } }; // no
>> X c { { .a = { .y = 5, .x = 5 } }; // ok
>
> How about:
>
> X = { .a.x = 5, .a.y = 5 }; // ok or not?
>

I think yes. But my idea seems lacking
support. One of the co-authors does
not consider this subtle difference to be
teachable. The last time we discriminated
= {} from {} to support "C-only" rules was
in C++11, to limit brace elision, but later
we just enabled it to {} as well :(

Zhihao Yuan

غير مقروءة،
01‏/08‏/2017، 1:41:38 ص1‏/8‏/2017
إلى std-pr...@isocpp.org
On Wed, Jul 26, 2017 at 11:19 AM, 'Matt Calabrese' via ISO C++
Standard - Future Proposals <std-pr...@isocpp.org> wrote:
>
> IIUC, you can still get constant initialization and also avoid dependence on
> member order even in that case. It doesn't require designated initializers:
>
> https://godbolt.org/g/nSk8cr
>

This is such a kind of smart solution, which
I personally would be totally fine to practice,
but if being a leader on a team, I can't really
decide to make it into the coding style lol.

Thiago Macieira

غير مقروءة،
01‏/08‏/2017، 1:58:31 ص1‏/8‏/2017
إلى std-pr...@isocpp.org
On segunda-feira, 31 de julho de 2017 22:36:07 PDT Zhihao Yuan wrote:
> > C++ developers usually add constructors.
>
> That will be too ideal. There is a difference
> between you break intentionally, and you
> break without knowing...

By adding or replacing a meber with a non-trivial one or by adding an explicit
constructor, a lot of things change. For example, until we get destructive
moves, std::vector will suddenly become less efficient if you make your type
non-trivial. Developers have to, unfortunately, be quite aware of the issue.

Again, my worry is C developers who aren't used to C++ limitations.

Victor Dyachenko

غير مقروءة،
01‏/08‏/2017، 3:23:48 ص1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
On Monday, July 31, 2017 at 9:05:10 PM UTC+3, Nicol Bolas wrote:
Well, the C++ language doesn't work well with that, since members must be initialized in the order they're presented in the class definition. In C++, changing the order of members is not a backwards-incompatible change, and designated initializers will not change that.

POSIX for example never specifies the order of members. Also it doesn't specify full set of members, just required ones:

The <time.h> header shall declare the timespec structure, which shall include at least the following members:

time_t  tv_sec    Seconds. 
long    tv_nsec   Nanoseconds. 

So when we write cross-platform code we want sometimes use structure initialization but don't want depend on members order and exact number of them:

struct TimeSpec : public ::timespec
{
    constexpr TimeSpec(time_t sec, long nsec)
        : ::timespec{.tv_sec = sec, .tv_nsec = nsec} {}
};

The order of initialization must not change! There is the only initialization order in C++: order of members declaration. The compiler can emit warning as with constructor init-list. Can't we just adopt the same rule as for constructor init-list? (And I don't think that this rule was a mistake as it was stated above)

Victor Dyachenko

غير مقروءة،
01‏/08‏/2017، 3:32:14 ص1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
More examples

const ::iovec v[] =
{
    ::iovec{.iov_base = buf1, .iov_len = buf1_len },
    ::iovec{.iov_base = buf2, .iov_len = buf2_len }
};
::writev(fd, v, array_size(v));

constexpr sockaddr_in my_endpoint =
{
    .sin_family = AF_INET,
    .sin_port = htons(123),
    .sin_addr = INADDR_ANY
};

Magnus Fromreide

غير مقروءة،
01‏/08‏/2017، 4:26:50 ص1‏/8‏/2017
إلى std-pr...@isocpp.org
System headers contains lots of insanity.
This is copied from Linux/glibc.

struct sigaction
{
/* Signal handler. */
#ifdef __USE_POSIX199309
union
{
/* Used if SA_SIGINFO is not set. */
__sighandler_t sa_handler;
/* Used if SA_SIGINFO is set. */
void (*sa_sigaction) (int, siginfo_t *, void *);
}
__sigaction_handler;
# define sa_handler __sigaction_handler.sa_handler
# define sa_sigaction __sigaction_handler.sa_sigaction
#else
__sighandler_t sa_handler;
#endif
/* more members follows */
};

This also talks against disallowing member.submember = value initialization
since it is quite reasonable to assume that

sigaction sa = { .sa_handler = &function };

should work.

/MF

Nicol Bolas

غير مقروءة،
01‏/08‏/2017، 10:36:03 ص1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals

The proof that the rule was a mistake is that compilers warn about it. If it wasn't something worth warning people about, they wouldn't put the warning there. So there is clearly an intent that the writer of the constructor ought to put them in the right order.

And the same goes here.People generally don't like to leave spurious warnings in their code if they can help it (this is 90% of the impetus for allowing `container::size_type` to be signed). So the only code where this warning will crop up is code where the user needs to declare them out of order (as in the POSIX case).

If it's something that shouldn't be done (hence the warning), then we should forbid it outright. And if it's something that should be done, then we should do it in such a way that it does exactly what it looks like it does.

Victor Dyachenko

غير مقروءة،
01‏/08‏/2017، 11:12:00 ص1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
On Tuesday, August 1, 2017 at 5:36:03 PM UTC+3, Nicol Bolas wrote:
The proof that the rule was a mistake is that compilers warn about it. If it wasn't something worth warning people about, they wouldn't put the warning there. So there is clearly an intent that the writer of the constructor ought to put them in the right order.

Again. The intention was to keep the simple and  universal rule:

Initialization order always defined by members declaration order

We don't want to change this rule with introducing one more initialization style.

Nicol Bolas

غير مقروءة،
01‏/08‏/2017، 11:47:08 ص1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals

But if you allow designated initializers to be specified out of member declaration order, it now stops on another "simple and universal rule":

Expression in braced-init-lists are sequenced in the order they appear

So, if you allow unordered designated initializers, which rule wins? Either the members will be initialized out of order, or the expressions will be evaluated out of order.

Victor Dyachenko

غير مقروءة،
01‏/08‏/2017، 11:50:51 ص1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
On Tuesday, August 1, 2017 at 6:47:08 PM UTC+3, Nicol Bolas wrote:
But if you allow designated initializers to be specified out of member declaration order, it now stops on another "simple and universal rule":

Expression in braced-init-lists are sequenced in the order they appear

So, if you allow unordered designated initializers, which rule wins? Either the members will be initialized out of order, or the expressions will be evaluated out of order.

Which rule wins now  in case of constructors init-list? Apply the same rule here.

Nicol Bolas

غير مقروءة،
01‏/08‏/2017، 11:55:47 ص1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals

Member initializers don't follow braced-init-list rules, and they never did. My point is that "apply the same rule here" is to violate the braced-init-list rule. Member initializers never had a contrary rule to be broken.

You're going to have to break one rule or the other, and I don't see why we should want either to be violated.

Victor Dyachenko

غير مقروءة،
01‏/08‏/2017، 12:02:37 م1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
If sequencing of initializers evaluation order the only issue here, I'm OK with allowing only initializers w/o side effects.

I strongly consider this feature as "named vs positional parameters". It is absolutely useless if we can't use it in order to not depend on declaration order and presence of unexpected members.

Code like

struct C { int a, b, c; };
C c{.c =1, a. = 2};

must "just work"

Victor Dyachenko

غير مقروءة،
01‏/08‏/2017، 12:09:30 م1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals

As one can see, all my examples are related to communication with C-world, i.e. used only with POD-structs. For C++ we usually can add constructor with predefined parameters order not related to the struct layout. So for me it's enough.

Nicol Bolas

غير مقروءة،
01‏/08‏/2017، 12:12:50 م1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
On Tuesday, August 1, 2017 at 12:02:37 PM UTC-4, Victor Dyachenko wrote:
If sequencing of initializers evaluation order the only issue here, I'm OK with allowing only initializers w/o side effects.

I'm not. "side effects" is a very broad term, and they can happen due to pretty much anything. You're effectively saying that you can't have designated initializers to call any function (that isn't `constexpr`). Even a seemingly innocuous expression like `a + b` could have an overloaded operator+ that isn't `constexpr` that could have a side effect.

Don't look at this feature from just the perspective of initializing a bunch of integers for some POSIX interface.

I strongly consider this feature as "named vs positional parameters". It is absolutely useless if we can't use it in order to not depend on declaration order and presence of unexpected members.

Code like

struct C { int a, b, c; };
C c{.c =1, a. = 2};

must "just work"

The problem with positional arguments is the question of what the argument means, not the fact that it has a fixed position. By attaching a name to the argument, you now have given some semantic meaning to it. This is true regardless of whether you have to give such arguments in a specific order.

So I strongly disagree that this feature is "useless" without the ability to change the argument's position.

Victor Dyachenko

غير مقروءة،
01‏/08‏/2017، 1:01:27 م1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
On Tuesday, August 1, 2017 at 7:12:50 PM UTC+3, Nicol Bolas wrote:
Don't look at this feature from just the perspective of initializing a bunch of integers for some POSIX interface.

I would say  "OS interfaces". They almost always use C. Also system libraries intended to be used not only from C++ (they even can be written in C++ but expose only C interface like ZeroMQ).
C is a lingua franca of computer languages.

Nicol Bolas

غير مقروءة،
01‏/08‏/2017، 1:52:53 م1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
On Tuesday, August 1, 2017 at 1:01:27 PM UTC-4, Victor Dyachenko wrote:
On Tuesday, August 1, 2017 at 7:12:50 PM UTC+3, Nicol Bolas wrote:
Don't look at this feature from just the perspective of initializing a bunch of integers for some POSIX interface.

I would say  "OS interfaces". They almost always use C.
 
But they "almost always" consider the order of their struct members to be part of their interface. That's why I listed POSIX explicitly: it doesn't specify a required order (or apparently, an exhaustive parameter list at all).

Also system libraries intended to be used not only from C++ (they even can be written in C++ but expose only C interface like ZeroMQ).

Does ZeroMQ consider the sequence of elements in structs to be mutable, yet still expect such changes to be backwards compatible? If not, then it's irrelevant.

This isn't about C interfaces. It's about stupid C interfaces (or more charitably, C interfaces that have specification deficiencies). That is a (very narrow) subset of C interfaces.

Thiago Macieira

غير مقروءة،
01‏/08‏/2017، 2:06:50 م1‏/8‏/2017
إلى std-pr...@isocpp.org
On terça-feira, 1 de agosto de 2017 10:52:53 PDT Nicol Bolas wrote:
> On Tuesday, August 1, 2017 at 1:01:27 PM UTC-4, Victor Dyachenko wrote:
> > On Tuesday, August 1, 2017 at 7:12:50 PM UTC+3, Nicol Bolas wrote:
> >> Don't look at this feature from just the perspective of initializing a
> >> bunch of integers for some POSIX interface.
> >
> > I would say "OS interfaces". They almost always use C.
>
> But they "almost always" consider the order of their struct members to be
> part of their interface. That's why I listed POSIX explicitly: it doesn't
> specify a required order (or apparently, an exhaustive parameter list at
> all).

Not really. Have you seen how many stat() implementations Linux has? If you
didn't know there was more than one, you've made my point.

https://code.woboq.org/linux/linux/arch/x86/include/uapi/asm/stat.h.html

[I'm not counting the new statx(2) syscall and struct.]

Did you also know that the sigaction is different between glibc and the
kernel, so that glibc can change it if it needs to?

https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/bits/
sigaction.h.html#24
https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/
kernel_sigaction.h.html#14
(oh, there's also an "old_kernel_sigaction"!)

> > Also system libraries intended to be used not only from C++ (they even can
> > be written in C++ but expose only C interface like ZeroMQ).

As well as the entire C library in MSVC 2015 and up and most of the macOS base
libraries.

> Does ZeroMQ consider the sequence of elements in structs to be mutable, yet
> still expect such changes to be backwards compatible? If not, then it's
> irrelevant.

No more than we do in C++, with inline namespaces. The use of inline
namespaces for *versioning* causes the same kind of problems.

Nicol Bolas

غير مقروءة،
01‏/08‏/2017، 3:12:25 م1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals

So what? Are you telling me that Microsoft is free to arbitrarily change the order of the elements in such structs? If not, then bringing them up is irrelevant.

Remember: the primary motivation for this feature is having to deal with structs where member order is not well specified. Microsoft's libraries specify member order for structs; they are a fundamental part of the ABI for their APIs. They will no more change that order than they will remove Win32.

And there are many other C APIs that work the same way. Vulkan for example; the order of members in that API is a fundamental part of its ABI. It will never change, and therefore it can be relied upon.

The only C APIs that are relevant to this discussion are those where the API says that it is free to change the order of the structures at any time.

Thiago Macieira

غير مقروءة،
01‏/08‏/2017، 3:41:43 م1‏/8‏/2017
إلى std-pr...@isocpp.org
On terça-feira, 1 de agosto de 2017 12:12:25 PDT Nicol Bolas wrote:
> > > > Also system libraries intended to be used not only from C++ (they even
> > > > can
> > > > be written in C++ but expose only C interface like ZeroMQ).
> >
> > As well as the entire C library in MSVC 2015 and up and most of the macOS
> > base
> > libraries.
>
> So what? Are you telling me that Microsoft is free to arbitrarily change
> the order of the elements in such structs? If not, then bringing them up is
> *irrelevant*.

They used to do that with EVERY release of their compiler up until MSVC 2015,
when they made a commitment to retain binary compatibility.

Even the official Win32 API has such shenanigans, like this:

https://sourceforge.net/p/mingw-w64/code/HEAD/tree/trunk/mingw-w64-headers/
include/iptypes.h#l107

In this case, one of the structs is a prefix of the other, but they could
easily be distinguished by the Length member.

> Remember: the primary motivation for this feature is having to deal with
> structs where member order is not well specified. Microsoft's libraries
> specify member order for structs; they are a fundamental part of the ABI
> for their APIs. They will no more change that order than they will remove
> Win32.

See above.

> The only C APIs that are relevant to this discussion are those where the
> API says that it is free to change the order of the structures at any time.

Or the way that glibc has done it: the struct *and* the function that takes it
change in lockstep. They've done it before. And help from the ELF versioning
system, you will not notice which of the two versions of the function you're
calling (that's the part that is like the C++11 inline namespaces).

That is binary incompatible if you pass that struct between two libraries of
yours that don't update in lockstep. Just like C++11 inline namespaces.

Bengt Gustafsson

غير مقروءة،
01‏/08‏/2017، 4:50:57 م1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
@Nicol: Your argument that "so far brace initializers have always had their expression evaluated in order" is irrelevant as it is just as true to say that "so far initializers have always had their expressions evalutated in member declaration order" as both orders are the same as long as we don't have designators! We have one precedent for what rule to use when the members are named: member initializer lists in constructors. Here the rule is that the expressions run in member declaration order, regardless of which order they are written. Not making these orders the same issues a warning in some compiler due to the same fear that you have: someone will at some point use an uninitialized member to initialize another member having *forgotten* the rules of the language.

For the designator case you want to strengthen this rule into a mandatory error if designators are placed in a different order than the member declarations. Firstly I see this as a bad idea just because it causes the two ways to initiate members to work differently.

As for the "arbitrarily complex expressions" that would prevent the compiler from finding a reference to a member later in the list I don't see this as plausible as it would require sending the whole object to a function in the midst of its initialization or sending the forward refered member itself to a function, which is as easily detected. Maybe if you take the address of the forward refered member and send it to a function that defers it, but that's piling two unlikely construct on top of each other.

Further, here are a couple of observations:

1) using one member to initiate another is very seldom useful as doing so would more or less indicate that you store the same thing twice. In practice I know that I need this extremely seldom, except for giving the address of one member to the constructor of another for future reference. Much more common is to send "this" itself to some member's constructor, which has its own caveats but which no initiation order limitation can alleviate.

2) Making sure that the member init list is in the same order as the member declarations is very tedious and causes lots of warnings especially when adding members. The programmer time spent on doing this tedious work is likely to be many times more than the time lost in finding those rare bugs that thinking that members are initialized in the order that expressions are written would have caused. I think this is safe to say given the extremely low frequency of usage of member a to init member b. Many programmers don't even know that you can do this, as they never needed it.

3) Even with the same initialization order it is easy to make a mistake once you start using one member's value in another members initializer expression either because you didin't think about the ordering or because you re-sorted the members without thinking about it, just reordering the ctor expressions mechanically to silence the warnings.

4) The order of members in a constructor's member initializer list is much closer to the member declaration list and thus easier to maintain than initializers at arbitrary distance from the struct declaration.

While we should of course try to prevent likely bugs with the language rules, I'm convinced that this feature would be much less convenient with this strict ordering rule, while at the same time catching *very few* bugs.

Nicol Bolas

غير مقروءة،
01‏/08‏/2017، 5:53:08 م1‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
On Tuesday, August 1, 2017 at 4:50:57 PM UTC-4, Bengt Gustafsson wrote:
@Nicol: Your argument that "so far brace initializers have always had their expression evaluated in order" is irrelevant as it is just as true to say that "so far initializers have always had their expressions evalutated in member declaration order" as both orders are the same as long as we don't have designators! We have one precedent for what rule to use when the members are named: member initializer lists in constructors. Here the rule is that the expressions run in member declaration order, regardless of which order they are written. Not making these orders the same issues a warning in some compiler due to the same fear that you have: someone will at some point use an uninitialized member to initialize another member having *forgotten* the rules of the language.

For the designator case you want to strengthen this rule into a mandatory error if designators are placed in a different order than the member declarations.

I contest your analogy. As stated by the paper itself, designated initializers is an extension of aggregate initialization, a convenience feature. Changing the order of either the evaluation of the expressions or the order of initialization takes it into the territory of a new feature, not a convenient shortcut.

Firstly I see this as a bad idea just because it causes the two ways to initiate members to work differently.

To the extent that your analogy makes sense, I see your idea as bad because it causes two braced-init-lists to evaluate their expressions in different orders.

It's all a question of whose rules you consider sacrosanct.

As for the "arbitrarily complex expressions" that would prevent the compiler from finding a reference to a member later in the list I don't see this as plausible as it would require sending the whole object to a function in the midst of its initialization or sending the forward refered member itself to a function, which is as easily detected. Maybe if you take the address of the forward refered member and send it to a function that defers it, but that's piling two unlikely construct on top of each other.

Further, here are a couple of observations:

1) using one member to initiate another is very seldom useful as doing so would more or less indicate that you store the same thing twice. In practice I know that I need this extremely seldom, except for giving the address of one member to the constructor of another for future reference. Much more common is to send "this" itself to some member's constructor, which has its own caveats but which no initiation order limitation can alleviate.

While the use of a member in another member's initializer is one problem, it is not the only problem that evaluation order matters for.

Remember: braced-init-lists are one of the very few places where C++ guarantees that the visible order of expressions represents the actual order of their evaluation. As such, it is very easy to have two expressions be dependent on each other.

Now granted, for designated initializers it's not nearly as easy to accidentally rely on as with regular braced-init-lists (since you can't use `...` pack expansion). But it is still very possible for you to rely on the visible order of expressions, particularly deliberately. After all, guaranteed evaluation order is a feature of braced-init-lists.

Another reason why I contest the member initializer comparison is because of context. Member initializers can only appear in constructors. Indeed, they appear before the actual constructor code. As such, you're fairly limited as to what side-effects you can rely on. Dependency on order of evaluation would thus be through side effects of function calls on parameters, globals, and the state of other members.

By contrast, braced-init-lists can appear pretty much anywhere. As such, there is a lot more local state to foil around with. It's very easy for you to use `++` gymnastics on some local variable and create an ordering dependency.

And remember: braced-init-lists guarantee order of evaluation, so right now:

int i = 20;
Agg a{++i, ++i};

That is completely, 100% well-defined. It does exactly what it says it does, in exactly the order it appears to.

2) Making sure that the member init list is in the same order as the member declarations is very tedious and causes lots of warnings especially when adding members. The programmer time spent on doing this tedious work is likely to be many times more than the time lost in finding those rare bugs that thinking that members are initialized in the order that expressions are written would have caused. I think this is safe to say given the extremely low frequency of usage of member a to init member b. Many programmers don't even know that you can do this, as they never needed it.

Considering that compilers tend to warn about having an out-of-order member initializer list, I contest this observation. If it were so safe and normal, why would there be a warning about it?
 
3) Even with the same initialization order it is easy to make a mistake once you start using one member's value in another members initializer expression either because you didin't think about the ordering or because you re-sorted the members without thinking about it, just reordering the ctor expressions mechanically to silence the warnings.

4) The order of members in a constructor's member initializer list is much closer to the member declaration list and thus easier to maintain than initializers at arbitrary distance from the struct declaration.

That sounds like a really good justification not to do this. The order of expression evaluation, while well-defined, cannot be locally known; it is determined by code "at an arbitrary distance from" the actual object initialization. Why is it a good thing to have the evaluation order of local expression evaluation be controlled due to code contained so far away?

Given the following code:

int i = 20;
Agg a{.mem1 = ++i, .mem2 = ++i};

The way I see it, there are only two reasonable answers for what this code does. Either the values of `mem1` and `mem2` are 21 and 22, or their values are unspecified. That is, either the expressions evaluate in the order they appear, or they evaluate in an order that is unspecified. C++ has lots of places where evaluation order is unspecified, and it has lots of places where evaluation order is locally specified.

But C++ only has one place where the evaluation order is well-and-non-locally-specified. And it's a place that compilers routinely warn about.

To have them evaluate in an order which is both well-defined and defined non-locally is the wrong answer.

Now, if you want to say that the expressions evaluate in the order they appear, but the members are initialized from those expressions in the order they appear in the struct, that might be workable. Though you're still going to have to explain how the possible conversions from the expression type to the member type.

But those expressions should not be evaluated in a well-and-non-locally-defined order.

Bengt Gustafsson

غير مقروءة،
02‏/08‏/2017، 11:47:58 ص2‏/8‏/2017
إلى ISO C++ Standard - Future Proposals


Den tisdag 1 augusti 2017 kl. 23:53:08 UTC+2 skrev Nicol Bolas:
On Tuesday, August 1, 2017 at 4:50:57 PM UTC-4, Bengt Gustafsson wrote:
@Nicol: Your argument that "so far brace initializers have always had their expression evaluated in order" is irrelevant as it is just as true to say that "so far initializers have always had their expressions evalutated in member declaration order" as both orders are the same as long as we don't have designators! We have one precedent for what rule to use when the members are named: member initializer lists in constructors. Here the rule is that the expressions run in member declaration order, regardless of which order they are written. Not making these orders the same issues a warning in some compiler due to the same fear that you have: someone will at some point use an uninitialized member to initialize another member having *forgotten* the rules of the language.

For the designator case you want to strengthen this rule into a mandatory error if designators are placed in a different order than the member declarations.

I contest your analogy. As stated by the paper itself, designated initializers is an extension of aggregate initialization, a convenience feature. Changing the order of either the evaluation of the expressions or the order of initialization takes it into the territory of a new feature, not a convenient shortcut.
That's all in the eye of the beholder. I don't care what you call it. I'm just saying that we should give ease of use a little thought, not making the feature harder to use than needed to avoid some tiny pitfall risk. 

Firstly I see this as a bad idea just because it causes the two ways to initiate members to work differently.

To the extent that your analogy makes sense, I see your idea as bad because it causes two braced-init-lists to evaluate their expressions in different orders.

It's all a question of whose rules you consider sacrosanct.

The fact is that in ALL places that members are initialized it is done in declaration order today. The only place that the language allows the textual order to diverge is in a member initializer list. To me designated initializers seem very similar to member initializer list, so the rule should be the same.
 

As for the "arbitrarily complex expressions" that would prevent the compiler from finding a reference to a member later in the list I don't see this as plausible as it would require sending the whole object to a function in the midst of its initialization or sending the forward refered member itself to a function, which is as easily detected. Maybe if you take the address of the forward refered member and send it to a function that defers it, but that's piling two unlikely construct on top of each other.

Further, here are a couple of observations:

1) using one member to initiate another is very seldom useful as doing so would more or less indicate that you store the same thing twice. In practice I know that I need this extremely seldom, except for giving the address of one member to the constructor of another for future reference. Much more common is to send "this" itself to some member's constructor, which has its own caveats but which no initiation order limitation can alleviate.

While the use of a member in another member's initializer is one problem, it is not the only problem that evaluation order matters for.

Remember: braced-init-lists are one of the very few places where C++ guarantees that the visible order of expressions represents the actual order of their evaluation. As such, it is very easy to have two expressions be dependent on each other. 

Now granted, for designated initializers it's not nearly as easy to accidentally rely on as with regular braced-init-lists (since you can't use `...` pack expansion). But it is still very possible for you to rely on the visible order of expressions, particularly deliberately. After all, guaranteed evaluation order is a feature of braced-init-lists.

Another reason why I contest the member initializer comparison is because of context. Member initializers can only appear in constructors. Indeed, they appear before the actual constructor code. As such, you're fairly limited as to what side-effects you can rely on. Dependency on order of evaluation would thus be through side effects of function calls on parameters, globals, and the state of other members.

By contrast, braced-init-lists can appear pretty much anywhere. As such, there is a lot more local state to foil around with. It's very easy for you to use `++` gymnastics on some local variable and create an ordering dependency.
And just about every style guide forbids doing that. What you are advocatring is that everyone should learn to use ++ gymnastics in braced init lists (but nowhere else) just because some incident or arcane rule made the expression order mandated. To me it seems that this order guarantee is a rule not worth learning.
 

And remember: braced-init-lists guarantee order of evaluation, so right now:

int i = 20;
Agg a{++i, ++i};

That is completely, 100% well-defined. It does exactly what it says it does, in exactly the order it appears to.
See above. 

2) Making sure that the member init list is in the same order as the member declarations is very tedious and causes lots of warnings especially when adding members. The programmer time spent on doing this tedious work is likely to be many times more than the time lost in finding those rare bugs that thinking that members are initialized in the order that expressions are written would have caused. I think this is safe to say given the extremely low frequency of usage of member a to init member b. Many programmers don't even know that you can do this, as they never needed it.

Considering that compilers tend to warn about having an out-of-order member initializer list, I contest this observation. If it were so safe and normal, why would there be a warning about it?
I don't know. Because the world is full of people who think other people can't learn how stuff works maybe? I maintain that this warning is detrimental as it consumes more time to get rid of than is saved by helping discover very few bugs.

 
 
3) Even with the same initialization order it is easy to make a mistake once you start using one member's value in another members initializer expression either because you didin't think about the ordering or because you re-sorted the members without thinking about it, just reordering the ctor expressions mechanically to silence the warnings.

4) The order of members in a constructor's member initializer list is much closer to the member declaration list and thus easier to maintain than initializers at arbitrary distance from the struct declaration.

That sounds like a really good justification not to do this. The order of expression evaluation, while well-defined, cannot be locally known; it is determined by code "at an arbitrary distance from" the actual object initialization. Why is it a good thing to have the evaluation order of local expression evaluation be controlled due to code contained so far away?
Or just put in the style guide, if it is not already there: Don't rely on the evaluation order of the individual expressions! Clear and simple rule.

Given the following code:

int i = 20;
Agg a{.mem1 = ++i, .mem2 = ++i};

The way I see it, there are only two reasonable answers for what this code does. Either the values of `mem1` and `mem2` are 21 and 22, or their values are unspecified. That is, either the expressions evaluate in the order they appear, or they evaluate in an order that is unspecified. C++ has lots of places where evaluation order is unspecified, and it has lots of places where evaluation order is locally specified.
I would not write this code. Nor would most programmers, with or without designators. As stated above I think it is best to just forget that evaluation order happens to be well defined (in the non-designator case at least).
 

But C++ only has one place where the evaluation order is well-and-non-locally-specified. And it's a place that compilers routinely warn about.

To have them evaluate in an order which is both well-defined and defined non-locally is the wrong answer.
Just assume that evaluation order is undefined if there are any designators then. 

Now, if you want to say that the expressions evaluate in the order they appear, but the members are initialized from those expressions in the order they appear in the struct, that might be workable. Though you're still going to have to explain how the possible conversions from the expression type to the member type.
No that would be complicating things unnecessarily. 


Hyman Rosen

غير مقروءة،
02‏/08‏/2017، 1:35:18 م2‏/8‏/2017
إلى std-pr...@isocpp.org
For what it's worth, in C, which also has designated initializers, the standard says:
    The evaluations of the initialization list expressions are indeterminately sequenced with
    respect to one another and thus the order in which any side effects occur is unspecified.152)
    152) In particular, the evaluation order need not be the same as the order of subobject initialization.

Bengt Gustafsson

غير مقروءة،
03‏/08‏/2017، 5:23:03 ص3‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
Thanks for that valuable piece of information. I think conforming to the same feature in C is a very good idea. Especially as it is consistent with my latest suggestion :-)

Zhihao Yuan

غير مقروءة،
04‏/08‏/2017، 5:28:21 م4‏/8‏/2017
إلى std-pr...@isocpp.org
No thanks, that's a very bad idea. When there
is a code, there is an order. Innocent users may
embed orders in their code without realizing it,
and changing the member order at the library
side will remote control user code's logic. We
are motivated by delivering features to support
sustainable libraries, not cursed libraries.

Ricardo Fabiano de Andrade

غير مقروءة،
05‏/08‏/2017، 2:44:35 م5‏/8‏/2017
إلى std-pr...@isocpp.org
Based on examples provided by others, I don't see cursed libraries but legacy systems (Posix, Win32...), which are industry standards for many years and certainly will be around for many more.

Supporting -at least- this use case for POD-types by not enforcing order would be beneficial both in developer productivity (don't have to deal with ordering) and portability (among platforms order is irrelevant).

From a practical point of view, constraining designated initializer to declaration order will be a burden and most likely won't be used as much.

Unless, that's the goal - being annoying to use by design. In this case, I would rather not have it in the language.

--
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/CAGsORuDwzS%3DR8pjCoaiBU2GRDKh_%3D8_bdQ6aMGdHxaBcbNLehg%40mail.gmail.com.

Zhihao Yuan

غير مقروءة،
06‏/08‏/2017، 12:29:34 ص6‏/8‏/2017
إلى std-pr...@isocpp.org
On Sat, Aug 5, 2017 at 1:44 PM, Ricardo Fabiano de Andrade
<ricardofabi...@gmail.com> wrote:
> Based on examples provided by others, I don't see cursed libraries but
> legacy systems (Posix, Win32...), which are industry standards for many
> years and certainly will be around for many more.
>

Cursed, newly written, libraries.

> Supporting -at least- this use case for POD-types by not enforcing order
> would be beneficial both in developer productivity (don't have to deal with
> ordering) and portability (among platforms order is irrelevant).
>

The raised use cases have already being
covered by https://godbolt.org/g/nSk8cr (Matt),
what you are asking for right now is to solve
a solved problem in a specific form rather
than solving a problem itself.

> From a practical point of view, constraining designated initializer to
> declaration order will be a burden and most likely won't be used as much.
>

There is nothing to support your claim.

> Unless, that's the goal - being annoying to use by design. In this case, I
> would rather not have it in the language.
>

It is okay if you don't share our motivations,
but if you don't even bother reading the first
page of the paper,

http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0329r0.pdf

, I have little to help.

Magnus Fromreide

غير مقروءة،
06‏/08‏/2017، 3:48:40 ص6‏/8‏/2017
إلى std-pr...@isocpp.org
On Sat, Aug 05, 2017 at 11:29:29PM -0500, Zhihao Yuan wrote:
> On Sat, Aug 5, 2017 at 1:44 PM, Ricardo Fabiano de Andrade
> <ricardofabi...@gmail.com> wrote:
> > Based on examples provided by others, I don't see cursed libraries but
> > legacy systems (Posix, Win32...), which are industry standards for many
> > years and certainly will be around for many more.
>
> Cursed, newly written, libraries.

Yes, you disagree with their style but please do not force that disagreement
upon the rest of us.

> > Supporting -at least- this use case for POD-types by not enforcing order
> > would be beneficial both in developer productivity (don't have to deal with
> > ordering) and portability (among platforms order is irrelevant).
> >
>
> The raised use cases have already being
> covered by https://godbolt.org/g/nSk8cr (Matt),
> what you are asking for right now is to solve
> a solved problem in a specific form rather
> than solving a problem itself.
>
> > From a practical point of view, constraining designated initializer to
> > declaration order will be a burden and most likely won't be used as much.
>
> There is nothing to support your claim.
>
> > Unless, that's the goal - being annoying to use by design. In this case, I
> > would rather not have it in the language.
>
> It is okay if you don't share our motivations,
> but if you don't even bother reading the first
> page of the paper,
>
> http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0329r0.pdf

On that very first page one of the motivations you are listing is

"To increase interoperability between C and C++"

but you are then arguing for the use of a lambda construct rather than
designated initializers in cases where C programs would use designated
initializers.

Is this feature intended to increase interoperability or not?

I would also claim that this restriction works against your stated motivation
"Towards more flexible and sustainable aggregate initialization" since you are
putting limitations on the implementation of the structure that wasn't there
before.

Please remember that the godbolt solution works equally well for all
variations and that if we import designated initializers from C99 as is and
limit them to trivial types then that is more interoperable than this proposal.

/MF

Zhihao Yuan

غير مقروءة،
06‏/08‏/2017، 5:57:38 ص6‏/8‏/2017
إلى std-pr...@isocpp.org
On Sun, Aug 6, 2017 at 2:48 AM, Magnus Fromreide <ma...@lysator.liu.se> wrote:
> On Sat, Aug 05, 2017 at 11:29:29PM -0500, Zhihao Yuan wrote:
>> http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0329r0.pdf
>
> On that very first page one of the motivations you are listing is
>
> "To increase interoperability between C and C++"
>
> but you are then arguing for the use of a lambda construct rather than
> designated initializers in cases where C programs would use designated
> initializers.
>
> Is this feature intended to increase interoperability or not?
>

Yes, and it already does. It allows library authors
to form a header embedded with designated
initializers supporting both C and C++. More
specifically, take

https://code.woboq.org/linux/linux/arch/x86/include/uapi/asm/stat.h.html

as an example, the macros like

#define INIT_STRUCT_STAT_PADDING(st) do { \
st.__unused4 = 0; \
st.__unused5 = 0; \
} while (0)

can be replaced with an initializer macro (in the
style of PTHREAD_MUTEX_INITIALIZER)
using designated initializers.

Magnus Fromreide

غير مقروءة،
06‏/08‏/2017، 6:27:50 ص6‏/8‏/2017
إلى std-pr...@isocpp.org
On Sun, Aug 06, 2017 at 04:57:35AM -0500, Zhihao Yuan wrote:
> On Sun, Aug 6, 2017 at 2:48 AM, Magnus Fromreide <ma...@lysator.liu.se> wrote:
> > On Sat, Aug 05, 2017 at 11:29:29PM -0500, Zhihao Yuan wrote:
> >> http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0329r0.pdf
> >
> > On that very first page one of the motivations you are listing is
> >
> > "To increase interoperability between C and C++"
> >
> > but you are then arguing for the use of a lambda construct rather than
> > designated initializers in cases where C programs would use designated
> > initializers.
> >
> > Is this feature intended to increase interoperability or not?
> >
>
> Yes, and it already does. It allows library authors to form a header
> embedded with designated initializers supporting both C and C++.

Library authors are about the only people who don't need this feature (sure,
it is nice but not needed that much) since they are in control of the
internals of their structs and thus they can use old style struct
initialization safely.

This is why some libraries contain initialization macros like
PTHREAD_MUTEX_INITIALIZER.

> More specifically, take
>
> https://code.woboq.org/linux/linux/arch/x86/include/uapi/asm/stat.h.html
>
> as an example, the macros like
>
> #define INIT_STRUCT_STAT_PADDING(st) do { \
> st.__unused4 = 0; \
> st.__unused5 = 0; \
> } while (0)
>
> can be replaced with an initializer macro (in the
> style of PTHREAD_MUTEX_INITIALIZER)
> using designated initializers.

No, they can't. If you read the comment right above that macro then you will
see that the point of it is to initialize just the padding members while the
the rest of the struct is left untouched, so in this case deferred
initialization is what the author is looking for.

This file also demonstrates, in the alternate definition of that very macro
on lines 106-111, that initialization of array members is a used feature.

/MF

Bengt Gustafsson

غير مقروءة،
06‏/08‏/2017، 1:24:30 م6‏/8‏/2017
إلى ISO C++ Standard - Future Proposals
I thought the C99 compatibility aspect would resound more, especially as there is no C++ specific issues in the argumentation for setting a different rule: Using uninitialized fields is the same (tiny) problem in C as in C++ struct initialization.

Another aspect is upgrading from C to C++. With stricter rules in C++ this puts additional burden on those who want to move from C to C++, and I think we should make this upgrade path as smooth as possible...

Zhihao Yuan

غير مقروءة،
06‏/08‏/2017، 1:57:52 م6‏/8‏/2017
إلى std-pr...@isocpp.org
On Sun, Aug 6, 2017 at 5:27 AM, Magnus Fromreide <ma...@lysator.liu.se> wrote:
>
> Library authors are about the only people who don't need this feature (sure,
> it is nice but not needed that much) since they are in control of the
> internals of their structs and thus they can use old style struct
> initialization safely.
>

Are you denying our needs? :)

>> can be replaced with an initializer macro (in the
>> style of PTHREAD_MUTEX_INITIALIZER)
>> using designated initializers.
>
> No, they can't. If you read the comment right above that macro then you will
> see that the point of it is to initialize just the padding members while the
> the rest of the struct is left untouched, so in this case deferred
> initialization is what the author is looking for.
>

Just an example, as long as you understand
the pattern.

> This file also demonstrates, in the alternate definition of that very macro
> on lines 106-111, that initialization of array members is a used feature.
>

That can simply be replaced by

st.__unused = { 0, 0, 0 }

Zhihao Yuan

غير مقروءة،
06‏/08‏/2017، 2:09:23 م6‏/8‏/2017
إلى std-pr...@isocpp.org
On Sun, Aug 6, 2017 at 12:24 PM, Bengt Gustafsson
<bengt.gu...@beamways.com> wrote:
> Another aspect is upgrading from C to C++. With stricter rules in C++ this
> puts additional burden on those who want to move from C to C++, and I think
> we should make this upgrade path as smooth as possible...
>

Narrowing conversion rules are an precedent
as stricter rules, and we see it works well.

Ricardo Fabiano de Andrade

غير مقروءة،
07‏/08‏/2017، 9:56:13 ص7‏/8‏/2017
إلى std-pr...@isocpp.org
On Sat, Aug 5, 2017 at 11:29 PM, Zhihao Yuan <z...@miator.net> wrote:
On Sat, Aug 5, 2017 at 1:44 PM, Ricardo Fabiano de Andrade
<ricardofabianodeandrade@gmail.com> wrote:
> Based on examples provided by others, I don't see cursed libraries but
> legacy systems (Posix, Win32...), which are industry standards for many
> years and certainly will be around for many more.
>

Cursed, newly written, libraries.


I understand that if strict ordering is not enforced, new code may rely on it and either:
1) break, because there are dependencies among members.
2) have the performance affected, because it requires temporaries, as mentioned in the proposal.

For 1, compilers may be smart enough to be able to detect the situation and warn accordingly.
For 2, I am willing to pay this cost in name of usability if there was a way to safely opt-in, at least for POD-types.

> Supporting -at least- this use case for POD-types by not enforcing order
> would be beneficial both in developer productivity (don't have to deal with
> ordering) and portability (among platforms order is irrelevant).
>

The raised use cases have already being
covered by https://godbolt.org/g/nSk8cr (Matt),
what you are asking for right now is to solve
a solved problem in a specific form rather
than solving a problem itself.


You're telling me that:
struct A { int x; int y; int z; ...a zillion members... };
A aVeryLongAndMeaningfulName;
aVeryLongAndMeaningfulName.x = 1
aVeryLongAndMeaningfulName.y = 2
aVeryLongAndMeaningfulName.z = 3
...repeat the variable name a zillion times...

It's the same as:
A aVeryLongAndMeaningfulName = { .x = 1, .y = 2, .z = 3 ...just initialization... };

From the perspective of the user, it's not. The former adds lot of noise.
 
> From a practical point of view, constraining designated initializer to
> declaration order will be a burden and most likely won't be used as much.
>

There is nothing to support your claim.


I really can't predict the future with such certainty.
But most people give up easily on something that doesn't work according to their expectations and first impressions.
 
> Unless, that's the goal - being annoying to use by design. In this case, I
> would rather not have it in the language.
>

It is okay if you don't share our motivations,
but if you don't even bother reading the first
page of the paper,

  http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0329r0.pdf

, I have little to help.


Yes, I've read the paper and it feels like it strips down the C99 designed initializers a bit too much.
I understand the way the proposal was written may be what's needed in order to make it pass through the standardization process though.

I'll step back from my previous rumbling...
Let's say it may be worth something testing the waters with the proposal as is and improve the support for designated initializers over time.
However, strict ordering was something of an issue that may not be "forward compatible" and therefore worth to fight for in name of usability.
 
--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________

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

Zhihao Yuan

غير مقروءة،
07‏/08‏/2017، 12:27:09 م7‏/8‏/2017
إلى std-pr...@isocpp.org
On Mon, Aug 7, 2017 at 8:56 AM, Ricardo Fabiano de Andrade
<ricardofabi...@gmail.com> wrote:
>
> I understand that if strict ordering is not enforced, new code may rely on
> it and either:
> 1) break, because there are dependencies among members.
> 2) have the performance affected, because it requires temporaries, as
> mentioned in the proposal.
>
> For 1, compilers may be smart enough to be able to detect the situation and
> warn accordingly.
> For 2, I am willing to pay this cost in name of usability if there was a way
> to safely opt-in, at least for POD-types.
>

Show me your plans.

>
> You're telling me that:
> struct A { int x; int y; int z; ...a zillion members... };
> A aVeryLongAndMeaningfulName;
> aVeryLongAndMeaningfulName.x = 1
> aVeryLongAndMeaningfulName.y = 2
> aVeryLongAndMeaningfulName.z = 3
> ...repeat the variable name a zillion times...
>
> It's the same as:
> A aVeryLongAndMeaningfulName = { .x = 1, .y = 2, .z = 3 ...just
> initialization... };
>
> From the perspective of the user, it's not. The former adds lot of noise.
>

You can fix that with

A aVeryLongAndMeaningfulName = []
{
A a;
a.x = 1,
a.y = 2,
a.z = 3,
...repeat `a` and always `a`...
return a;
}();

> But most people give up easily on something that doesn't work according to
> their expectations and first impressions.
>

That's exactly how most people react to
C++'s `volatile` keyword, and guess what,
we still have little to solve the "problem" :/

> Yes, I've read the paper and it feels like it strips down the C99 designed
> initializers a bit too much.
>

You can feel so, but the truth is, we started
from trying to understand what does
designated initialization really mean for C++
and the first thing we realized was that it must
be a form of initialization, rather than, for
example, assignment.

> However, strict ordering was something of an issue that may not be "forward
> compatible" and therefore worth to fight for in name of usability.
>

I think we eliminated potential forward compatible
issues, for example, we deliberately deployed rules
to not allow written orders to affect overload
resolution.
الرد على الكل
رد على الكاتب
إعادة توجيه
0 رسالة جديدة