Discussion:
[std-proposals] Designated initializer list status?
l***@gmail.com
2015-11-21 14:11:37 UTC
Permalink
Coming from Golang and C I was saddened by the fact that standard C++ lacks
(JSON-esque) designated initializer list support.

Some pragmatic reasons to support it:
1. It seem to be some interest in this feature, e.g. 33
votes: http://stackoverflow.com/questions/18731707
2. It's makes C++ more compatible with C99.
3. Many compilers already seem have support for it. I realized it wasn't in
C++ when I turned on --pedantic.

A contrived example:

// ...

class Link {
public:
int bw;
Coord wr_c;
Coord bl_c;
int src;
int mtime;
Computer* dst;
int u;
};

// ...

int main() {
// ...
std::vector<Link> links(m);
for (int i = 0; i < m; i++) {
int a, b, c;
std::cin >> a >> b >> c;
links[i] = {c, Coord(), Coord(), a, 0, &comps[b]}; //
Non-designated
links[i] = {.src = a, .dst = &comps[b], .bw = c}; // Designated
}
// ...
}

Some (likely highly subjective) problems with the non-designated syntax:
1. Order dependent (harder to read).
2. Does not scale well, parameter ordering is increasingly difficult to
track when more fields are added.
3. Does not allow explicit initialization of a partial set of fields.
(harder to read, annoying to write)

1, 2: Understanding the designated initializer list assignment requires no
declaration or parameter order knowledge so the grammar is more context
free and therefore easier to read.

3: You are forced to write an explicit constructor or enumerate defaults
for all fields. An explicit constructor would require extra boilerplate and
introduce another independent order you have to track (more context).
Writing a constructor for a specific way you want to initialize a class
seems like code smell.

So I hope this feature would become standardized. Thank you for reading my
humble opinion.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Andrew Tomazos
2015-11-21 15:22:34 UTC
Permalink
https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/IgDFqKjKlRs
Post by l***@gmail.com
Coming from Golang and C I was saddened by the fact that standard C++
lacks (JSON-esque) designated initializer list support.
http://stackoverflow.com/questions/18731707
2. It's makes C++ more compatible with C99.
3. Many compilers already seem have support for it. I realized it wasn't
in C++ when I turned on --pedantic.
// ...
class Link {
int bw;
Coord wr_c;
Coord bl_c;
int src;
int mtime;
Computer* dst;
int u;
};
// ...
int main() {
// ...
std::vector<Link> links(m);
for (int i = 0; i < m; i++) {
int a, b, c;
std::cin >> a >> b >> c;
links[i] = {c, Coord(), Coord(), a, 0, &comps[b]}; //
Non-designated
links[i] = {.src = a, .dst = &comps[b], .bw = c}; // Designated
}
// ...
}
1. Order dependent (harder to read).
2. Does not scale well, parameter ordering is increasingly difficult to
track when more fields are added.
3. Does not allow explicit initialization of a partial set of fields.
(harder to read, annoying to write)
1, 2: Understanding the designated initializer list assignment requires no
declaration or parameter order knowledge so the grammar is more context
free and therefore easier to read.
3: You are forced to write an explicit constructor or enumerate defaults
for all fields. An explicit constructor would require extra boilerplate and
introduce another independent order you have to track (more context).
Writing a constructor for a specific way you want to initialize a class
seems like code smell.
So I hope this feature would become standardized. Thank you for reading
my humble opinion.
--
---
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
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-proposals/.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-21 15:27:50 UTC
Permalink
Post by l***@gmail.com
Coming from Golang and C I was saddened by the fact that standard C++
lacks (JSON-esque) designated initializer list support.
http://stackoverflow.com/questions/18731707
... and?
Post by l***@gmail.com
2. It's makes C++ more compatible with C99.
That's not a good reason for anything. C and C++ are different languages,
with divergent goals.
Post by l***@gmail.com
3. Many compilers already seem have support for it. I realized it wasn't
in C++ when I turned on --pedantic.
That's the best reason you've given thus far, but it's still pretty weak.
Post by l***@gmail.com
// ...
class Link {
int bw;
Coord wr_c;
Coord bl_c;
int src;
int mtime;
Computer* dst;
int u;
};
// ...
int main() {
// ...
std::vector<Link> links(m);
for (int i = 0; i < m; i++) {
int a, b, c;
std::cin >> a >> b >> c;
links[i] = {c, Coord(), Coord(), a, 0, &comps[b]}; //
Non-designated
links[i] = {.src = a, .dst = &comps[b], .bw = c}; // Designated
}
// ...
}
1. Order dependent (harder to read).
2. Does not scale well, parameter ordering is increasingly difficult to
track when more fields are added.
3. Does not allow explicit initialization of a partial set of fields.
(harder to read, annoying to write)
Says who? C++11 has always allowed it (the remainder are either value or
default initialized, I forget which). And C++14 made it so that if you use
default member initializers, they will be used if you don't specialize an
initializer with braced-init-syntax <http://ideone.com/c1ubCd>:

struct A
{
int foo;
int bar = 7;
float jay = 12.784f;
};

A a{5, 11};
a.jay == 12.784f;

1, 2: Understanding the designated initializer list assignment requires no
Post by l***@gmail.com
declaration or parameter order knowledge so the grammar is more context
free and therefore easier to read.
3: You are forced to write an explicit constructor or enumerate defaults
for all fields. An explicit constructor would require extra boilerplate and
introduce another independent order you have to track (more context).
Writing a constructor for a specific way you want to initialize a class
seems like code smell.
And what about the rest of the idea? braced-init-lists in C++ don't work
like they do in C, after all. You can use them in contexts where you select
between different function overloads, picking the overload that has a
constructor that matches the provided elements. How does that work with
your designated initializers?
Post by l***@gmail.com
So I hope this feature would become standardized. Thank you for reading
my humble opinion.
I hope you don't think that merely making a post to a mailing list will get
it standardized.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Andrew Tomazos
2015-11-21 15:43:59 UTC
Permalink
Post by Nicol Bolas
Post by l***@gmail.com
Coming from Golang and C I was saddened by the fact that standard C++
lacks (JSON-esque) designated initializer list support.
http://stackoverflow.com/questions/18731707
... and?
Post by l***@gmail.com
2. It's makes C++ more compatible with C99.
That's not a good reason for anything. C and C++ are different languages,
with divergent goals.
C compatibility is very important for C++. The fact that designated
initializer lists have existing practice, especially in C, is a strong
point in favor of the feature as a C++ feature. It's not the only point
considered though.

So I hope this feature would become standardized. Thank you for reading my
Post by Nicol Bolas
Post by l***@gmail.com
humble opinion.
I hope you don't think that merely making a post to a mailing list will
get it standardized.
Right. This is at the stage where someone needs to write a detailed
proposal and have it presented and discussed at a meeting. Daryl Walker
drafted one in 2013, but I don't think it was submitted and/or presented.

One of the things I've been thinking about is that we could have a
std::designated_initializer_list unit type (no member functions) - and for
aggregate class types a constructor taking such a type is generated. A
constructor taking such a type could also be explicitly defaulted. We
could then have a syntactic construct designated-init-list.

For example:

struct S {
S(std::designated_initializer_list) = default; // implicit for
aggregate class types

int x, y, z;
}

S s = {.x = 1, .y = 2, .z = 3};

void f(S s);

int main() { f({.x = 1, .y = 2, .z = 3}); }
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
l***@gmail.com
2015-11-21 16:15:50 UTC
Permalink
Post by Nicol Bolas
Post by l***@gmail.com
1. Order dependent (harder to read).
2. Does not scale well, parameter ordering is increasingly difficult to
track when more fields are added.
3. Does not allow explicit initialization of a partial set of fields.
(harder to read, annoying to write)
Says who? C++11 has always allowed it (the remainder are either value or
default initialized, I forget which). And C++14 made it so that if you use
default member initializers, they will be used if you don't specialize an
Yes that's great, I actually do that in my example: "u" is not specified in
the non-designated syntax. But the "partial set" I mention could be fields
that are not a prefix of the fields declared in the class so there is no
suffix "reminder". E.g. you declare fields {0, 1, 2 .. 23} and you want to
explicitly initialize the fields {14, 17, 23}.
Post by Nicol Bolas
One of the things I've been thinking about is that we could have a
std::designated_initializer_list unit type (no member functions) - and for
aggregate class types a constructor taking such a type is generated. A
constructor taking such a type could also be explicitly defaulted. We
could then have a syntactic construct designated-init-list.
struct S {
S(std::designated_initializer_list) = default; // implicit for
aggregate class types
int x, y, z;
}
S s = {.x = 1, .y = 2, .z = 3};
void f(S s);
int main() { f({.x = 1, .y = 2, .z = 3}); }
I like this idea.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-21 17:56:48 UTC
Permalink
Post by Andrew Tomazos
Post by Nicol Bolas
Post by l***@gmail.com
Coming from Golang and C I was saddened by the fact that standard C++
lacks (JSON-esque) designated initializer list support.
http://stackoverflow.com/questions/18731707
... and?
Post by l***@gmail.com
2. It's makes C++ more compatible with C99.
That's not a good reason for anything. C and C++ are different languages,
with divergent goals.
C compatibility is very important for C++.
The committee doesn't seem to think so. Or at least, not compatibility with
features that aren't in C89. They don't want to have syntax step on each
other, but they don't want to adopt features from C99/11 directly into C++
either.

The fact that designated initializer lists have existing practice,
Post by Andrew Tomazos
especially in C, is a strong point in favor of the feature as a C++ feature.
Only to the extent that it proves that the feature is viable. Which,
admittedly, is a very useful thing to have.
Post by Andrew Tomazos
It's not the only point considered though.
So I hope this feature would become standardized. Thank you for reading
Post by Nicol Bolas
Post by l***@gmail.com
my humble opinion.
I hope you don't think that merely making a post to a mailing list will
get it standardized.
Right. This is at the stage where someone needs to write a detailed
proposal and have it presented and discussed at a meeting. Daryl Walker
drafted one in 2013, but I don't think it was submitted and/or presented.
It also wasn't particularly detailed
<https://raw.githubusercontent.com/CTMacUser/multiarray-iso-proposal/master/designation-proposal.html>.
Or at least, not very well written; the details were all in spec-language
wording rather than a human-readable explanation.

I would also suggest that any such proposal not have "improve parity with
C" as its primary motivation (or in the case of the above, *only*
motivation). More convincing motivations should revolve around using it in
C++ code. What can you do with it that you can't do without it. The fact
that it's in C is a good way to prove that the idea works, but that alone
is hardly sufficient motivation for a C++ feature.

For example, there was a discussion awhile back about named parameters
<https://groups.google.com/a/isocpp.org/d/msg/std-proposals/3iegpTsMuT0/hgmDlYkWIgAJ>.
It lead to the revelation that, if you have designated initializers, you
can more or less get that. The function would take a single struct type,
and you would call it with `funcName({.param1 = X, .param2 = Y, etc})`.
Naturally, they wanted to add language functionality to remove the parens,
but that's unnecessary. The main point is to give people an easy way to
pass named parameters to a function, and designated initializers would
allow precisely that.

Oh, and for someone coming up with such a proposal, don't forget that in
C++17, types with base classes that have variables will *also* be
considered aggregates. So you'll need to come up with a way to use them
with designated initializers too.

Good luck solving *that* one ;)
Post by Andrew Tomazos
One of the things I've been thinking about is that we could have a
std::designated_initializer_list unit type (no member functions) - and for
aggregate class types a constructor taking such a type is generated. A
constructor taking such a type could also be explicitly defaulted. We
could then have a syntactic construct designated-init-list.
I don't see the purpose of this. Generally speaking, most types that have
constructors have invariants, so their data members are private, so you
can't name them.

So when would you need such a constructor? Just say that if the
braced-init-list contains designated initializers, it uses aggregate
initialization instead of calling a constructor. And if the type is one
that cannot use aggregate initialization, then it's a compiler error.

The only place where I see this being useful is for a type that makes
everything public, but also provides constructors (and therefore can't use
aggregate initialization). Is that a sufficiently common usage scenario for
these? The point of a constructor is to provide and protect some kind of
invariant; we have default member initializers for simply setting default
values. So what was the point of providing that type with a constructor?

And don't forget: the last time we made special rules for how
braced-init-lists call constructors, we got screwed with the
`vector<int>/initializer_list<int>` problem. We shouldn't be making more
rules that break uniformity farther than it already has been.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Andrew Tomazos
2015-11-21 18:37:26 UTC
Permalink
Post by Nicol Bolas
Oh, and for someone coming up with such a proposal, don't forget that in
C++17, types with base classes that have variables will *also* be
considered aggregates. So you'll need to come up with a way to use them
with designated initializers too.
Good luck solving *that* one ;)
The simplest way would be to say that the base classes are default
constructed and the designated initializer list can only refer to the most
derived members.

struct D : B { int x; }
D d = { .x = 42}; // ok, B default constructed

Even simpler would say that the designated initializer list constructor is
only generated for classes without base classes.

struct D : B { int x; }
D d = { .x = 42}; // ill-formed

Another solution would be to allow .BaseType as a key in a designated
initializer list.

struct B { int y; }
struct D : B { int x; }
D d = { .B = {.y = 43}, .x = 42}; // ok

Another solution would be to roll up the base object graph into a single
list of members and the designated initializer list addresses only member
subobjects (and not base subobjects).

struct B { int y; }
struct D : B { int x; }
D d = { .x = 42, .y = 43}; // ok

The third and forth solutions are forward-compatible with the first and
second solutions.
Post by Nicol Bolas
Post by Andrew Tomazos
One of the things I've been thinking about is that we could have a
std::designated_initializer_list unit type (no member functions) - and for
aggregate class types a constructor taking such a type is generated. A
constructor taking such a type could also be explicitly defaulted. We
could then have a syntactic construct designated-init-list.
I don't see the purpose of this. Generally speaking, most types that have
constructors have invariants, so their data members are private, so you
can't name them.
Noone is suggesting being able to address private members. The reason I
suggest using a type, is for forwarding of designated initializer lists.
To actually consume them (as an endpoint) you need a compiler-generated
constructor - either implicitly or explicitly defaulted.

eg

struct S { int x,y; };
std::vector<S> v;
v.push_back({.x = 42, .y = 43});

So what was the point of providing that type with a constructor?
Apart from the above, its also so we can address the designated initializer
list constructor, to explicit default or explicitlty delete it.
Post by Nicol Bolas
And don't forget: the last time we made special rules for how
braced-init-lists call constructors, we got screwed with the
`vector<int>/initializer_list<int>` problem. We shouldn't be making more
rules that break uniformity farther than it already has been.
I'm not sure what you mean by the "`vector<int>/initializer_list<int>`
problem".
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Andrew Tomazos
2015-11-21 18:39:43 UTC
Permalink
Post by Andrew Tomazos
Post by Nicol Bolas
Oh, and for someone coming up with such a proposal, don't forget that in
C++17, types with base classes that have variables will *also* be
considered aggregates. So you'll need to come up with a way to use them
with designated initializers too.
Good luck solving *that* one ;)
The simplest way would be to say that the base classes are default
constructed and the designated initializer list can only refer to the most
derived members.
struct D : B { int x; }
D d = { .x = 42}; // ok, B default constructed
Even simpler would say that the designated initializer list constructor is
only generated for classes without base classes.
struct D : B { int x; }
D d = { .x = 42}; // ill-formed
Another solution would be to allow .BaseType as a key in a designated
initializer list.
struct B { int y; }
struct D : B { int x; }
D d = { .B = {.y = 43}, .x = 42}; // ok
Another solution would be to roll up the base object graph into a single
list of members and the designated initializer list addresses only member
subobjects (and not base subobjects).
struct B { int y; }
struct D : B { int x; }
D d = { .x = 42, .y = 43}; // ok
The third and forth solutions are forward-compatible with the first and
second solutions.
Post by Nicol Bolas
Post by Andrew Tomazos
One of the things I've been thinking about is that we could have a
std::designated_initializer_list unit type (no member functions) - and for
aggregate class types a constructor taking such a type is generated. A
constructor taking such a type could also be explicitly defaulted. We
could then have a syntactic construct designated-init-list.
I don't see the purpose of this. Generally speaking, most types that have
constructors have invariants, so their data members are private, so you
can't name them.
Noone is suggesting being able to address private members. The reason I
suggest using a type, is for forwarding of designated initializer lists.
To actually consume them (as an endpoint) you need a compiler-generated
constructor - either implicitly or explicitly defaulted.
eg
struct S { int x,y; };
std::vector<S> v;
v.push_back({.x = 42, .y = 43});
Sorry this is a bogus example. I meant something like:

struct S { int x,y; };
std::optional<S> s;
s.emplace({.x = 42, .y = 43});
Post by Andrew Tomazos
So what was the point of providing that type with a constructor?
Apart from the above, its also so we can address the designated
initializer list constructor, to explicit default or explicitlty delete it.
Post by Nicol Bolas
And don't forget: the last time we made special rules for how
braced-init-lists call constructors, we got screwed with the
`vector<int>/initializer_list<int>` problem. We shouldn't be making more
rules that break uniformity farther than it already has been.
I'm not sure what you mean by the "`vector<int>/initializer_list<int>`
problem".
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-21 20:03:37 UTC
Permalink
Post by Andrew Tomazos
Post by Nicol Bolas
Oh, and for someone coming up with such a proposal, don't forget that in
C++17, types with base classes that have variables will *also* be
considered aggregates. So you'll need to come up with a way to use them
with designated initializers too.
Good luck solving *that* one ;)
The simplest way would be to say that the base classes are default
constructed and the designated initializer list can only refer to the most
derived members.
...
Even simpler would say that the designated initializer list constructor is
Post by Andrew Tomazos
only generated for classes without base classes.
Those aren't solutions.The committee went through a bunch of effort to
allow braced-init-lists to work for aggregates with base class members.

Designated initializers are nothing more than a special form of aggregate
initialization. Why should they be any less functional than regular
aggregate initialization? You should not consider this case as something
you can choose not to cover.

Another solution would be to roll up the base object graph into a single
Post by Andrew Tomazos
list of members and the designated initializer list addresses only member
subobjects (and not base subobjects).
And what if the base class has a member of the same name as a derived class
member?
Post by Andrew Tomazos
Post by Nicol Bolas
Post by Andrew Tomazos
One of the things I've been thinking about is that we could have a
std::designated_initializer_list unit type (no member functions) - and for
aggregate class types a constructor taking such a type is generated. A
constructor taking such a type could also be explicitly defaulted. We
could then have a syntactic construct designated-init-list.
I don't see the purpose of this. Generally speaking, most types that have
constructors have invariants, so their data members are private, so you
can't name them.
Noone is suggesting being able to address private members. The reason I
suggest using a type, is for forwarding of designated initializer lists.
To actually consume them (as an endpoint) you need a compiler-generated
constructor - either implicitly or explicitly defaulted.
OK, you're now talking about a whole new problem, one that is completely
unrelated to this one. Namely, solving perfect forwarding for *construction*
via uniform initialization syntax. Being able to pass a braced-init-list to
a (template) function, which will pass that braced-init-list to initialize
a type.

Designated initializers are not special in needing a solution here. We need
this for uniform initialization, whether designated or not. And designated
initializers themselves don't need a solution to this problem for them to
still be effective.

Focus on one problem at a time. You can't use braced-init-lists through
`emplace` currently for aggregate initialization, so there's no reason to
expect designated initializers to be able to work there either.
Post by Andrew Tomazos
So what was the point of providing that type with a constructor?
Apart from the above, its also so we can address the designated
initializer list constructor, to explicit default or explicitlty delete it.
I think you have a different vision for this.

My vision for designated initializatizers is this: aggregate
initialization, where you can name the variable that gets a particular
value. You seem to want something esle.

You say that having a constructor allows you to declare it deleted, so as
to prevent using designated initializers for that type. Well... why would
you *want* to? What purpose does it serve, what problem does it solve? You
made your class members public. All designated initializer syntax is meant
to do is make it easy to fill those members in.

So why would you ever want to forbid users from using this syntax?

The only cases I can come up with where a "designated initialization
constructor" would be important would be a type that has both public
members *and* constructors. A type with constructors is by definition not
an aggregate and therefore cannot participate in aggregate initialization.
So if you want to use designated initializers with it, you need some other
way.

But there's a reason why we decided that types with constructors aren't
aggregates. Type constructors exist to ensure *invariants*, to ensure the
sanity of a type's interface and data. If all your members are public,
there are no invariants. Which means there is no need for constructors. Oh,
you might have some factory functions to make it easy to have different
sets of default data, or to specify some data while taking defaults from
elsewhere. Or whatever.

But if you're able to just initialize an object with whatever values you
like, if it's just a bundle of independent variables with no invariant,
then there is no point in that type having a constructor at all. So why
would it not be an aggregate?

Give me an example of a type who's members are public (and therefore you'd
want to use designated initializers) which also *needs* constructors. Or of
a type who's members are public and that there is a good reason to want to
forbid the use of designated initializers.

And don't forget: the last time we made special rules for how
Post by Andrew Tomazos
Post by Nicol Bolas
braced-init-lists call constructors, we got screwed with the
`vector<int>/initializer_list<int>` problem. We shouldn't be making more
rules that break uniformity farther than it already has been.
I'm not sure what you mean by the "`vector<int>/initializer_list<int>`
problem".
I'm referring to the fact that `vector<std::string> s{5};` will construct a
vector with 5 empty strings, but `vector<int> s{5}` will construct a vector
with a single integer with the value 5. This is because braced-init-list
syntax prefers a matching `initializer_list` constructor over regular
constructors. Template code has problems effectively using
braced-init-lists because it can't know if it might call an
initializer_list constructor instead of a regular one.

Indeed, one of the explicit examples for constexpr_if was to have an easy
way to work around such issues, using braces or parenthesis on a type's
initialization depending on whether the type `is_constructible` with the
arguments.

As I said, the last time we started adding special rules for
braced-init-list with constructors, we made uniform initialization
non-uniform. Let's not do that again.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Andrew Tomazos
2015-11-22 22:47:25 UTC
Permalink
Post by Nicol Bolas
Designated initializers are nothing more than a special form of aggregate
initialization.
That's a premature design decision. I agree generally it is desirable to
have consistency between { braced-init-list, std::initializer_list,
list-initialization } and { designated-init-list,
std::designated_initializer_list, designated-list-initialization }. It's
the proposal authors job to work out the details.

And what if the base class has a member of the same name as a derived class
Post by Nicol Bolas
member?
One option would be ill-formed ambiguous. Another would be to favor the
derived class. Another (compatible with both previous) would be to allow
the name to be qualified by .base_type::member_name. It's a corner case
though. You shouldn't really "override" data members by name to begin with.
Post by Nicol Bolas
Post by Andrew Tomazos
Post by Andrew Tomazos
One of the things I've been thinking about is that we could have a
Post by Andrew Tomazos
std::designated_initializer_list unit type (no member functions) - and for
aggregate class types a constructor taking such a type is generated. A
constructor taking such a type could also be explicitly defaulted. We
could then have a syntactic construct designated-init-list.
I don't see the purpose of this. Generally speaking, most types that
have constructors have invariants, so their data members are private, so
you can't name them.
Noone is suggesting being able to address private members. The reason I
suggest using a type, is for forwarding of designated initializer lists.
To actually consume them (as an endpoint) you need a compiler-generated
constructor - either implicitly or explicitly defaulted.
OK, you're now talking about a whole new problem, one that is completely
unrelated to this one. Namely, solving perfect forwarding for
*construction* via uniform initialization syntax. Being able to pass a
braced-init-list to a (template) function, which will pass that
braced-init-list to initialize a type.
That wasn't my intent. The intent was that the relationship between
braced-init-list and std::initializer_list be somehow similar to that
between designated-init-list and std::designated_initializer_list.

I see a braced-init-list as a compile-time ordered sequence (either
homogeneous or heterogeneous). It is the position index within the list
that is used as the key.

I see a designated-init-list as a compile-time associative array. It is
the explicit member name designated that is the key.

With that difference in mind, designated-init-list should generally be
integrated into the language in a similar way to how braced-init-list is.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-23 02:54:27 UTC
Permalink
Post by Nicol Bolas
Designated initializers are nothing more than a special form of aggregate
Post by Nicol Bolas
initialization.
That's a premature design decision.
No it isn't. It is an answer to the question, "What do you think the
feature is?" That is the most important question that a proposal can answer.

Believing that this is a premature question is how you get scope creep.
It's how the pre-C++11 concepts kept accruing cruft until it grew so large
that it collapsed under its own weight. It's how P0057 transformed from
being a simple continuation mechanism into handing generators and even
things that have nothing at all to do with concurrency or scheduling.

That kind of thinking leads to unfocused features, things that you can do
"because they're kinda related" rather than because they're actually part
of the design. It's bad design.

That's not to say that you shouldn't entertain different ways of achieving
something. But every solution must ultimately match up with the question of
what the feature is meant to accomplish. Deciding what the feature is must
always be step #1.
Post by Nicol Bolas
I agree generally it is desirable to have consistency between {
braced-init-list, std::initializer_list, list-initialization } and {
designated-init-list, std::designated_initializer_list,
designated-list-initialization }. It's the proposal authors job to work
out the details.
Or you could just... not do it this way. The feature of designated
initializers does not in any way require what you're talking about. If you
take away this and just make it a form of aggregate initialization, you
will have lost nothing *except* for the ability to forward this
initialization. Which, as previously stated, is a general problem with
braced-init-lists, which need not be solved here.

It's easy to say that a proposal should be much more complicated than it
needs to be, then say that it is someone else's job to figure out the
actual details to make it work.

And what if the base class has a member of the same name as a derived class
Post by Nicol Bolas
Post by Nicol Bolas
member?
One option would be ill-formed ambiguous. Another would be to favor the
derived class. Another (compatible with both previous) would be to allow
the name to be qualified by .base_type::member_name. It's a corner case
though. You shouldn't really "override" data members by name to begin with.
You don't have to be overriding anything to get a conflict. Maybe the base
class just happened to use the same variable name you wanted to use in your
derived class. Or maybe you have two base classes that again just happen to
have variables with the same name.

It may be a corner case, but C++ is a language of corner cases. Just
because you don't like this case doesn't mean you can choose not to account
for it.
Post by Nicol Bolas
One of the things I've been thinking about is that we could have a
Post by Nicol Bolas
Post by Andrew Tomazos
Post by Nicol Bolas
Post by Andrew Tomazos
std::designated_initializer_list unit type (no member functions) - and for
aggregate class types a constructor taking such a type is generated. A
constructor taking such a type could also be explicitly defaulted. We
could then have a syntactic construct designated-init-list.
I don't see the purpose of this. Generally speaking, most types that
have constructors have invariants, so their data members are private, so
you can't name them.
Noone is suggesting being able to address private members. The reason I
suggest using a type, is for forwarding of designated initializer lists.
To actually consume them (as an endpoint) you need a compiler-generated
constructor - either implicitly or explicitly defaulted.
OK, you're now talking about a whole new problem, one that is completely
unrelated to this one. Namely, solving perfect forwarding for
*construction* via uniform initialization syntax. Being able to pass a
braced-init-list to a (template) function, which will pass that
braced-init-list to initialize a type.
That wasn't my intent. The intent was that the relationship between
braced-init-list and std::initializer_list be somehow similar to that
between designated-init-list and std::designated_initializer_list.
Then your design does not express your intent, since they are nothing at
all alike. An `initializer_list` is merely a pair of pointers to a specific
type, which is backed by a temporary array created by the compiler. A
`designated_initializer_list` isn't even a *list*. As you suggest, it's
more like a map. But as I'll explain, it isn't even that.

I see a braced-init-list as a compile-time ordered sequence (either
Post by Nicol Bolas
homogeneous or heterogeneous). It is the position index within the list
that is used as the key.
You may see it however you wish, but the actual standard does not agree. A
braced-init-list is not even an expression, let alone an ordered sequence
of values. It is a compile-time construct used to initialize a type, and it
does not exist any longer than that.
Post by Nicol Bolas
I see a designated-init-list as a compile-time associative array. It is
the explicit member name designated that is the key.
... OK, I'll play along.

If the member name is the key, what is the "value" associated with that key?

Here's what I mean. Let's say I have this type:

struct T
{
string str;
vector<float> vec;
};

Which means I can do this:

T{.str = {"foo"}, .vec = {5, 0.3f}};

OK, now let's say I have some intermediate function:

void func(std::designated_initiailzer_list foo)
{
T t(foo);
}

func({.str = {"foo"}, .vec = {5, 0.3f}});

OK, so... what happens here? At the point of the call to `func`, the
compiler has no idea what `.str` and `.vec` refer to. And {"foo"} and {5,
0.3f} are braced-init-lists, which are neither values nor expressions. They
are compiler constructs used to initialize a type, but there is no type
associated with them. So... what gets stored in the
`designated_initializer_list`?

You can't have some API that allows you to access members of a
`designated_initializer_list` by name. Because that would require that you
could get a C++ value from them, and they don't necessarily contain values.

The only way for your `designated_initializer_list` type to work is by pure
compiler magic. That is, it's a completely opaque type, which at runtime
will be filled in by "some data". And therefore, the only way you could use
it is to pass it to someone else or to construct a type, as `func` does
above.

And that of course leaves open the question of what happens if the user
does this:

func({.str = {"foo"}, .vect = {5, 0.3f}});

T does not contain a `vect`. So... now what? Since `func` could be passed a
`designated_initializer_list` from anywhere, I guess this becomes some kind
of runtime exception thrown in compiler-generated code. But... that means
that the construction of `T` within `func` has to be a runtime
construction. That the compiler does not and cannot know what values get
filled in and which ones don't.

How would `func` go about verifying that it can actually construct a `T`
from a given `designated_initializer_list`? Would it have some API for
doing that, like `d_i_l.can_construct<T>();`? How expensive would that
function be? Almost certainly more expensive than regular old
`vector::emplace`.

Or... we could avoid solving any of those problems and just make designated
initialization a form of aggregate initialization.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Hannes
2015-11-23 10:50:06 UTC
Permalink
Post by Nicol Bolas
Or... we could avoid solving any of those problems and just make
designated initialization a form of aggregate initialization.
Yeah, I feel like that would be the simplest way to go now. Essentially
making designated initialization syntactically equivalent to a (non-array)
aggregate initialization where the ordering of keys is flexible and
maintain the same C++14 behaviour as aggregate initialization for
non-specified fields (default initialization).

That would be useful enough IMO and very easy to explain to people. "Oh,
it's just aggregate initialization with some extra syntactic sugar."
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-23 14:41:05 UTC
Permalink
Post by Hannes
Post by Nicol Bolas
Or... we could avoid solving any of those problems and just make
designated initialization a form of aggregate initialization.
Yeah, I feel like that would be the simplest way to go now. Essentially
making designated initialization syntactically equivalent to a (non-array)
aggregate initialization where the ordering of keys is flexible and
maintain the same C++14 behaviour as aggregate initialization for
non-specified fields (default initialization).
That would be useful enough IMO and very easy to explain to people. "Oh,
it's just aggregate initialization with some extra syntactic sugar."
There are three areas of concern when it comes to designated initializers
in C++, which have to do with C++-specific features.

The first is base class member initialization. I think the best way to
handle it syntactically is like this:

{ .base_class = {.mem1 = 5, .mem2 = {init}}, .outer1 = {1, 2, 3} };

That is, each class level gets its own initializer list. I think this is
cleaner than doing `.base_class.mem1`, and it avoids the issue of classes
in the hierarchy with the same name. It also makes it clear that you're
setting members in a subobject.

Oh, and it allows you to initialize subobjects of the main class as you see
fit. We could have initialized `.base_class` without naming its members,
for example.

There is a downside to this approach. That downside is due to the second
issue: how designated initializers interact with non-designated
initializers in the same braced-init-list.

If you have this:

{3, "string", .member = 12};

How does that work? Do we initialize all of the designated members first,
then order the rest and initialize them if they were not initialized? How
does that interact with base class members?

I don't know much about designated initializers in C, so how do they handle
it?

The third issue is overloading. Consider the following code:

struct agg1
{
int i; float f;
};

struct agg2
{
std::string s; std::string g;
};

void func(agg1) {std::cout << "func1\n";}
void func(agg2) {std::cout << "func2\n";}

int main() {
func({5, 2.0f});
return 0;
}

Under C++14 rules, this will actually select the correct overload to call,
based on whether the type can be constructed given the braced-init-list.
Designated initializers should not change this. Which means they should
allow differentiation on the basis of member names:

struct agg1
{
int i; float f;
};

struct agg2
{
int i; float j;
};

void func(agg1) {std::cout << "func1\n";}
void func(agg2) {std::cout << "func2\n";}

int main() {
func({.i = 5, .j = 2.0f}); //Calls second overload.
return 0;
}

That's an extra bit of complexity.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Sean Middleditch
2015-11-23 17:05:28 UTC
Permalink
Post by Nicol Bolas
Post by Hannes
Post by Nicol Bolas
Or... we could avoid solving any of those problems and just make
designated initialization a form of aggregate initialization.
Yeah, I feel like that would be the simplest way to go now. Essentially
making designated initialization syntactically equivalent to a (non-array)
aggregate initialization where the ordering of keys is flexible and
maintain the same C++14 behaviour as aggregate initialization for
non-specified fields (default initialization).
That would be useful enough IMO and very easy to explain to people. "Oh,
it's just aggregate initialization with some extra syntactic sugar."
There are three areas of concern when it comes to designated initializers
in C++, which have to do with C++-specific features.
The first is base class member initialization. I think the best way to
{ .base_class = {.mem1 = 5, .mem2 = {init}}, .outer1 = {1, 2, 3} };
That is, each class level gets its own initializer list. I think this is
cleaner than doing `.base_class.mem1`, and it avoids the issue of classes
in the hierarchy with the same name. It also makes it clear that you're
setting members in a subobject.
What about base initializes for:

struct derived : base<int>, base<float> { ... };

You can't use the base type's name anymore as a disambiguator. Would the
initializer specification have to allow specifying the template
specialization?

Does the base initialization support aliased names?
Post by Nicol Bolas
Oh, and it allows you to initialize subobjects of the main class as you
see fit. We could have initialized `.base_class` without naming its
members, for example.
There is a downside to this approach. That downside is due to the second
issue: how designated initializers interact with non-designated
initializers in the same braced-init-list.
{3, "string", .member = 12};
How does that work? Do we initialize all of the designated members first,
then order the rest and initialize them if they were not initialized? How
does that interact with base class members?
I would imagine it would work identically to defaulted function parameters
- they're just call-sight syntactical helpers and have no effect on the
callee's code at all.

Relatedly, think of constructors. A syntax that only works for initializing
a type with public members is not _that_ useful (though it's certainly not
useless). I think that we need to solve named parameters first (preferably
IMO using a similar syntax to C99's designated initializers) so that you
can use the exact same syntax for initializing a simple type as you do for
initializing a class with user-defined constructors.

I say that requires the default arguments solution because initializing a
class with constructors requires letting a class designer mark a
constructor's parameter names as being part of the API (because we
certainly don't want to do that always!), which is 99.98% the remaining
major design problem with named function parameters.

I'm currently a fan of just using the .name syntax in both the declaration
and call-site, which keeps syntactical parity with C99 designated
initializers, but there's still some hairier corners of C++ I haven't
thought through fully yet, so I'm not sure if that approach works.

It'd be "nice" IMO though to get:

(a) function(.foo = 1, .bar = "gaz"); // invoke a function
(b) constructor{.foo = 1, .bar = "gaz"); // construct a temporary
(c) type name = {.foo = 1, .bar = "gaz"}; // initialize a local
(d) void declaration(int .foo, string .bar); // declare a
function/constructor

I'm still rolling it around in my head, though.
Post by Nicol Bolas
I don't know much about designated initializers in C, so how do they
handle it?
Overloading is indeed the tricky bit that needs a lot of careful thought
and study from anyone planning to actually propose any of this.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-23 19:04:20 UTC
Permalink
Post by Sean Middleditch
Post by Nicol Bolas
Post by Hannes
Post by Nicol Bolas
Or... we could avoid solving any of those problems and just make
designated initialization a form of aggregate initialization.
Yeah, I feel like that would be the simplest way to go now. Essentially
making designated initialization syntactically equivalent to a (non-array)
aggregate initialization where the ordering of keys is flexible and
maintain the same C++14 behaviour as aggregate initialization for
non-specified fields (default initialization).
That would be useful enough IMO and very easy to explain to people. "Oh,
it's just aggregate initialization with some extra syntactic sugar."
There are three areas of concern when it comes to designated initializers
in C++, which have to do with C++-specific features.
The first is base class member initialization. I think the best way to
{ .base_class = {.mem1 = 5, .mem2 = {init}}, .outer1 = {1, 2, 3} };
That is, each class level gets its own initializer list. I think this is
cleaner than doing `.base_class.mem1`, and it avoids the issue of classes
in the hierarchy with the same name. It also makes it clear that you're
setting members in a subobject.
struct derived : base<int>, base<float> { ... };
You can't use the base type's name anymore as a disambiguator. Would the
initializer specification have to allow specifying the template
specialization?
The type names for the base classes are `base<int>` and `base<float>`.
`base` itself is not a typename; it's the name of a template.
Post by Sean Middleditch
Relatedly, think of constructors. A syntax that only works for
initializing a type with public members is not _that_ useful (though it's
certainly not useless). I think that we need to solve named parameters
first (preferably IMO using a similar syntax to C99's designated
initializers) so that you can use the exact same syntax for initializing a
simple type as you do for initializing a class with user-defined
constructors.
Perfect is the enemy of good. I don't see a *need* to make designated
initializers dependent on general named parameter solutions.

Besides, the committee seems to hate named parameters on general principle.
And there will be general resistance to designated initializers out of
fears that it will lead to de-facto named parameter usage by using structs
and designated initializers.

Our goal should be to avoid giving them that impression.

Also, we could add wording to allow brace elision to save you:

class SomeType
{
public:
struct ConstructorFields{ int foo; vector<int> bar; float def = 42.0f}
SomeType(ConstructorFields cf);
SomeType(int i);

private:
};

SomeType t = {.foo = 4, .bar = {1, 2, 3}};

Adding another set of braces would have allowed this to work. We just need
wording to make those braces unnecessary, if the braced-init-list doesn't
match up with `SomeType`'s argument list, but it does match up with one of
the single-argument constructors. And designated initializers by definition
cannot call a constructor, so they never match.

Then again, we could just make people use double braces, relying on
existing brace elision rules to get rid of them:

SomeType t1 = {{5}}; //Calls single integer constructor.
SomeType t2 = {{.foo = 4, .bar = {1, 2, 3}}}; //Calls named parameter
constructor.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Ville Voutilainen
2015-11-23 19:17:24 UTC
Permalink
Post by Nicol Bolas
Besides, the committee seems to hate named parameters on general principle.
Correction: the committee has multiple times rejected proposals that don't work.
The parameter names in the library are unspecified, which means that none
of the proposals seen thus far would work with the library. Various aspiring
proposal authors continue to cite e.g. Common Lisp and Python as examples
where named parameters "just work". The difference there is three-fold:
1) in those languages, the library parameter names are well-specified
2) those languages do not suffer from preprocessor macros leaking into
the library implementation, whether standard library or not
3) library authors, even authors of non-standard libraries, have reservations
of having to start supporting stable names once the names become part
of the interface.

All proposals for named parameters have thus far failed to reconcile
those concerns,
regardless of the actual syntax they have proposed.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-23 20:34:27 UTC
Permalink
Post by Nicol Bolas
Post by Nicol Bolas
Besides, the committee seems to hate named parameters on general
principle.
Correction: the committee has multiple times rejected proposals that don't work.
The parameter names in the library are unspecified, which means that none
of the proposals seen thus far would work with the library. Various aspiring
proposal authors continue to cite e.g. Common Lisp and Python as examples
1) in those languages, the library parameter names are well-specified
2) those languages do not suffer from preprocessor macros leaking into
the library implementation, whether standard library or not
3) library authors, even authors of non-standard libraries, have reservations
of having to start supporting stable names once the names become part
of the interface.
All proposals for named parameters have thus far failed to reconcile
those concerns,
regardless of the actual syntax they have proposed.
Six on one hand, half-a-dozen on the other. Having named parameters means
parameter names are part of a function's interface. So if people are
against that, then they're against named parameters.

That being said, I cannot verify if any of the named parameter proposals
were ever opt-in. That is, that a function declaration had to use specific
syntax to allow itself to be used with named parameters. All of the ones I
found seem to assume that any function that gives its parameters names must
be callable with named parameters.

Requiring opt-in on a per-function basis would seem to satisfy #3: each
library author decides if they want to allow the use of named parameters
for each function. And if they do, then it is their responsibility to keep
the names legitimate.

#2 is hardly a fair concern to castigate named parameters over. Macro
leakage potentially hurts lots of things, from uses of user-defined
literals to many other features. Modules will do what can be done to
prevent this, but we shouldn't avoid features out of fear of what macro
leakage will cause.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Thiago Macieira
2015-11-23 23:33:52 UTC
Permalink
Post by Nicol Bolas
Six on one hand, half-a-dozen on the other. Having named parameters means
parameter names are part of a function's interface. So if people are
against that, then they're against named parameters.
They're against making the ugly parameters currently used for various reasons
become API.

Examples:
libstdc++:
basic_string(const basic_string& __str, size_type __pos,
size_type __n, const _Alloc& __a)

libc++:
basic_string(const basic_string& __str, size_type __pos, size_type __n =
npos,
const allocator_type& __a = allocator_type());

MSVC (Dinkumware):
basic_string(const _Myt& _Right, size_type _Roff, size_type _Count,
const _Alloc& _Al)

Note how all three implementations use names reserved to the compiler (double
underscore or underscore + capital) to avoid someone #define'ing them and
causing problems.

This code does not compile on Solaris:

int sun, mercury, venus, earth, mars, jupiter, saturn, uranus, neptune;
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-24 03:47:38 UTC
Permalink
Post by Thiago Macieira
Post by Nicol Bolas
Six on one hand, half-a-dozen on the other. Having named parameters
means
Post by Nicol Bolas
parameter names are part of a function's interface. So if people are
against that, then they're against named parameters.
They're against making the ugly parameters currently used for various reasons
become API.
But as has been previously stated, the standard doesn't define those
parameter names. So they wouldn't be able to become part of the API. They
would be implementation defined, rather than standard defined.

And I'd point out that that nobody seems to be willing to stop concepts
from getting into C++17 just because it hasn't been applied to the standard
library.

But then again, the whole named parameters discussion is off-topic: this is
about designated initializers, which should not be conflated with named
parameters. After all, structure member names are already part of a
struct's API.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Sean Middleditch
2015-11-24 04:03:32 UTC
Permalink
Post by Nicol Bolas
But then again, the whole named parameters discussion is off-topic: this
is about designated initializers, which should not be conflated with named
parameters. After all, structure member names are already part of a
struct's API.

They are intimately related because of constructors.

You can't have good C++-y designated initializers supporting user-designed
classes without dealing with constructor parameters and their names at some
level.
Post by Nicol Bolas
--
---
You received this message because you are subscribed to a topic in the
Google Groups "ISO C++ Standard - Future Proposals" group.
Post by Nicol Bolas
To unsubscribe from this topic, visit
https://groups.google.com/a/isocpp.org/d/topic/std-proposals/875H9Elkhdw/unsubscribe
.
Post by Nicol Bolas
To unsubscribe from this group and all its topics, send an email to
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-proposals/.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-24 04:47:47 UTC
Permalink
Post by Sean Middleditch
Post by Nicol Bolas
But then again, the whole named parameters discussion is off-topic: this
is about designated initializers, which should not be conflated with named
parameters. After all, structure member names are already part of a
struct's API.
They are intimately related because of constructors.
You can't have good C++-y designated initializers supporting user-designed
classes without dealing with constructor parameters and their names at some
level.
Why not? I already explained how to get the equivalent behavior without
having actual named parameters (have the type take a struct and use an
extra set of braces).

Designated initializers name fields in data structures, not parameters for
constructors. They're not the same thing, and I see no reason to hold up a
genuinely useful feature like designated initailizers just because it
doesn't cover a particular case as nicely as you would prefer.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Thiago Macieira
2015-11-24 05:11:53 UTC
Permalink
Post by Nicol Bolas
Why not? I already explained how to get the equivalent behavior without
having actual named parameters (have the type take a struct and use an
extra set of braces).
It's not as efficient and is much more limited.

Suppose these two constructor examples:

struct S
{
S(int n);
S(int n, std::tuple<std::string, std::vector<int>> data);
};

vs

struct S
{
struct ConstructorArguments {
std::tuple<std::string, std::vector<int>> data;
int n;
};
S(ConstructorArguments);
};

The first thing to note is that we cannot create an overload, as otherwise
that would necessitate an ugly syntax specifying the structure type, as in:
S{(ConstructWithLength){ .n = 1 }};
S((ConstructWithLengthAndData){ .n = 1, .data = {"", {}}};
that is very C-like and C++-unlike.

The second thing is that, since we cannot create overloads, we conclude all
types in the constructor aggregates must be initialised before the constructor
itself is called. As the example shows, this makes it impossible to optimise
for a case where such a parameter isn't needed.

The third thing is that, with the lack of overloads, it's impossible to have
constructors with different behaviours. Think of these std::string
constructors:

basic_string(const value_type* s, size_type n, const allocator_type& a =
allocator_type());
basic_string(size_type n, value_type c, const allocator_type& a =
allocator_type());

The aggregate would need to contain both s and c, so how can the called
constructor know which of the two was actually intended to be used?

Finally, there's also the issue of efficiency for non-inline calls. Take again
the case of std::string with the second constructor above. The size_type and
value_type types are PODs and allocator_type is an empty type, so quite a few
ABIs simply pass the three values in plain registers. If you replace that with
an aggregate, those ABIs will need to spill the primitive values to a
temporary in the stack and pass that temporary's address to the constructor.

The same thing also prevents tail-call optimisations when you need to call a
function with a different object.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Hannes
2015-11-24 10:58:00 UTC
Permalink
Post by Thiago Macieira
Post by Nicol Bolas
Why not? I already explained how to get the equivalent behavior without
having actual named parameters (have the type take a struct and use an
extra set of braces).
It's not as efficient and is much more limited.
struct S
{
S(int n);
S(int n, std::tuple<std::string, std::vector<int>> data);
};
vs
struct S
{
struct ConstructorArguments {
std::tuple<std::string, std::vector<int>> data;
int n;
};
S(ConstructorArguments);
};
The first thing to note is that we cannot create an overload, as otherwise
S{(ConstructWithLength){ .n = 1 }};
S((ConstructWithLengthAndData){ .n = 1, .data = {"", {}}};
that is very C-like and C++-unlike.
I just noted that anonymous struct declaration is currently not allowed by
the language as parameter types. The proposal could allow them in the
specific case when you have constructors that only accept them in a single
argument. A designated aggregate initialization would only be able to call
this new allowed parameter form to prevent name leakage:

struct S {
// Constructor 1
S(struct {
int n;
});
// Constructor 2
S(struct {
std::tuple<std::string, std::vector<int>> data;
int n;
});
};

S v{3} // Constructor 1 (classic aggregate initialization)
S v{3, {"foo", {4, 2}}} // Constructor 2 (classic aggregate initialization)

S v{n: 3} // Constructor 1 (designated aggregate
initialization)
S v{n: 3, {"foo", {4, 2}}} // Constructor 2 (designated aggregate
initialization)

S v(3) // Constructor 1 (backwards-compatible)
S v(3, {"foo", {4, 2}}) // Constructor 2 (backwards-compatible)


The syntax would be completely analogous to having normal parameter lists
in terms of overloading priority. The only "quirk" would be the fact that
it wouldn't be a true anonymous struct because the constructor declaration
would be considered equivalent as long as it had the same members in the
anonymous struct with the same types but that would still be analogous with
normal parameter lists.

This would also solve your second and third concern (performance and
different behaviours). This could be argued to be a completely separate
feature though but it would be introduced at the same time to allow the
benefit of designated initilization in higher C++ as well.
Post by Thiago Macieira
Finally, there's also the issue of efficiency for non-inline calls. Take again
the case of std::string with the second constructor above. The size_type and
value_type types are PODs and allocator_type is an empty type, so quite a few
ABIs simply pass the three values in plain registers. If you replace that with
an aggregate, those ABIs will need to spill the primitive values to a
temporary in the stack and pass that temporary's address to the constructor.
Are those ABIs major ABIs? You make quite a few assumptions here about
modern ABIs. Just because you wrap a value in a POD doesn't mean it spills
to the stack.

Code (C):

typedef struct {
int a;
int b;
} t;

t foo() {
return (t) {.a = 1, .b = 2};
}

x86: (no stack)

foo():
movabsq $8589934593, %rax
ret

ARM64: (no stack)

foo():
mov x0, 0
mov x1, 1
bfi x0, x1, 0, 32
mov x1, 2
bfi x0, x1, 32, 32
ret

ARM: (no stack)

foo():
mov r2, r0
movw r3, #:lower16:.LANCHOR0
movt r3, #:upper16:.LANCHOR0
ldmia r3, {r0, r1}
stmia r2, {r0, r1}
mov r0, r2
bx lr

The same thing also prevents tail-call optimisations when you need to call
Post by Thiago Macieira
a
function with a different object.
How? The parameters you call a function with is irrelevant for TCO, even if
they spill onto the stack or change. The important part is that you return
the same type so that you can just overwrite the return pointer.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Hannes
2015-11-24 11:00:16 UTC
Permalink
Oops, bogus example, constructor 2 should be:

// Constructor 2
S(struct {
int n;
std::tuple<std::string, std::vector<int>> data;
});
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Thiago Macieira
2015-11-24 15:38:53 UTC
Permalink
Post by Hannes
Post by Andrew Tomazos
struct S
{
struct ConstructorArguments {
std::tuple<std::string, std::vector<int>> data;
int n;
};
S(ConstructorArguments);
};
The first thing to note is that we cannot create an overload, as otherwise
that would necessitate an ugly syntax specifying the structure type, as
S{(ConstructWithLength){ .n = 1 }};
S((ConstructWithLengthAndData){ .n = 1, .data = {"", {}}};
that is very C-like and C++-unlike.
I just noted that anonymous struct declaration is currently not allowed by
the language as parameter types. The proposal could allow them in the
specific case when you have constructors that only accept them in a single
argument.
I don't think anonymous structs are required. The point is that you should
never have to name them, so whether they are anonymous or not is entirely
orthogonal to the issue.
Post by Hannes
A designated aggregate initialization would only be able to call
struct S {
// Constructor 1
S(struct {
int n;
});
// Constructor 2
S(struct {
int n;
std::tuple<std::string, std::vector<int>> data;
});
};
S v{3} // Constructor 1 (classic aggregate initialization)
S v{3, {"foo", {4, 2}}} // Constructor 2 (classic aggregate initialization)
S v{n: 3} // Constructor 1 (designated aggregate
initialization)
S v{n: 3, {"foo", {4, 2}}} // Constructor 2 (designated aggregate
initialization)
S v(3) // Constructor 1 (backwards-compatible)
S v(3, {"foo", {4, 2}}) // Constructor 2 (backwards-compatible)
Doesn't all first entries in each pair cause an ambiguous overload resolution?
The parameter n appears in both structures and both structures have an
integral as their first argument.
Post by Hannes
Post by Andrew Tomazos
Finally, there's also the issue of efficiency for non-inline calls. Take again
the case of std::string with the second constructor above. The size_type and
value_type types are PODs and allocator_type is an empty type, so quite a few
ABIs simply pass the three values in plain registers. If you replace that with
an aggregate, those ABIs will need to spill the primitive values to a
temporary in the stack and pass that temporary's address to the constructor.
Are those ABIs major ABIs? You make quite a few assumptions here about
modern ABIs. Just because you wrap a value in a POD doesn't mean it spills
to the stack.
Try with three parameters instead of two. Try with any non-trivial type too.

And even with two, like in your example, note how the two ints got compressed
into one register, instead of being each passed in a register. This means the
callee has a more complex code path to work with each.
Post by Hannes
Post by Andrew Tomazos
The same thing also prevents tail-call optimisations when you need to call a
function with a different object.
How? The parameters you call a function with is irrelevant for TCO, even if
they spill onto the stack or change. The important part is that you return
the same type so that you can just overwrite the return pointer.
TCO requires that the callee can pop the stack of the caller. That cannot
happen if the caller had to allocate space for the parameters on the stack.

struct S { int a, b, c; };
void f(S);
void g(int a, int b, int c)
{
f({a, b, c});
}

The above on x86-64 requires spilling to the stack.

The point is simply that using a struct in lieu of actual parameters is not
guaranteed to have the same ABI calling conventions, which may prevent TCO
from happening and quite often will have worse performance for one reason or
another.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Hannes
2015-11-24 17:13:27 UTC
Permalink
Post by Thiago Macieira
I don't think anonymous structs are required. The point is that you should
never have to name them, so whether they are anonymous or not is entirely
orthogonal to the issue.
It solves the problem where the library would be forced to expose a new
standardized aggregate type name in the API to application code. Allowing
anonymous structs would allow the library to force the application code to
use unnamed initializer lists.
Post by Thiago Macieira
Doesn't all first entries in each pair cause an ambiguous overload resolution?
The parameter n appears in both structures and both structures have an
integral as their first argument.
Yes, this is currently ambiguous with aggregate initialization. However the
idea was to introduce more overloading priority rules to make it analogous
to parameter overloading, e.g. select the anonymous struct with the fewest
unspecified/default fields.

That said the anonymous struct thing is just a silly idea I had. Personally
I completely agree with Nicol that designated initializers should just be
considered an extension to aggregate initialization and has nothing to do
with this named parameter business.

TCO requires that the callee can pop the stack of the caller. That cannot
Post by Thiago Macieira
happen if the caller had to allocate space for the parameters on the stack.
You're right, I made a premature assumption that the memory was owned by
the callee but I now realize that would always force memory copy and thus
be inefficient.
Post by Thiago Macieira
The point is simply that using a struct in lieu of actual parameters is not
guaranteed to have the same ABI calling conventions, which may prevent TCO
from happening and quite often will have worse performance for one reason or
another.
This is getting really off-topic now IMO but my counterpoint to that would
be that if someone is concerned with the performance loss from some stack
spillage they should consider link-time optimization anyway which makes the
ABI irrelevant.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Thiago Macieira
2015-11-24 21:58:07 UTC
Permalink
Post by Hannes
That said the anonymous struct thing is just a silly idea I had. Personally
I completely agree with Nicol that designated initializers should just be
considered an extension to aggregate initialization and has nothing to do
with this named parameter business.
I'm not disputing that.

I'm simply saying that it will have limited use because the interesting
aggregates in C++ have non-trivial default constructors, which removes the
ability to use designated initialisers.

If, however, designated parameters were implemented, it would apply to
constructors too and seamlessly remove the limitation.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-24 15:12:01 UTC
Permalink
Post by Thiago Macieira
Post by Nicol Bolas
Why not? I already explained how to get the equivalent behavior without
having actual named parameters (have the type take a struct and use an
extra set of braces).
It's not as efficient and is much more limited.
... so?

Designated initializers exist to make aggregates work better and nicer.
They are *not* a way to back-door named parameters into the language, nor
are they an attempt to make constructors work with them.

The fact that they don't solve something that they're not actually supposed
to solve is *not* a problem with the feature. It's a problem with your
expectations and desires projected onto the feature.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Thiago Macieira
2015-11-24 15:22:25 UTC
Permalink
Post by Nicol Bolas
Post by Thiago Macieira
Post by Nicol Bolas
Why not? I already explained how to get the equivalent behavior without
having actual named parameters (have the type take a struct and use an
extra set of braces).
It's not as efficient and is much more limited.
... so?
Designated initializers exist to make aggregates work better and nicer.
They are *not* a way to back-door named parameters into the language, nor
are they an attempt to make constructors work with them.
The fact that they don't solve something that they're not actually supposed
to solve is *not* a problem with the feature. It's a problem with your
expectations and desires projected onto the feature.
I'm merely pointing out that you can't say that designated initialisers are a
replacement for function parameter initialisers. They are not.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Thiago Macieira
2015-11-24 04:54:14 UTC
Permalink
Post by Nicol Bolas
Post by Thiago Macieira
They're against making the ugly parameters currently used for various reasons
become API.
But as has been previously stated, the standard doesn't define those
parameter names. So they wouldn't be able to become part of the API. They
would be implementation defined, rather than standard defined.
The standard doesn't define, but all implementations have parameter names for
at least every parameter that is actually used in inline implementations. If
the proposal for designated initialisers will not require an a specialised
syntax for parameter names, then the existing, ugly names will be available
for library users.
Post by Nicol Bolas
And I'd point out that that nobody seems to be willing to stop concepts
from getting into C++17 just because it hasn't been applied to the standard
library.
That's only analogous to the case where designated initialisers for parameter
names requires a new syntax: until the library implementations "opt in" to the
parameter names, they can't be used.
Post by Nicol Bolas
But then again, the whole named parameters discussion is off-topic: this is
about designated initializers, which should not be conflated with named
parameters. After all, structure member names are already part of a
struct's API.
I consider this very much on topic. In C++, most useful aggregates have
constructors and unless we come up with a way for designating parameters to
constructors, designated initialisers will have a somewhat limited use for us.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-24 16:07:14 UTC
Permalink
Post by Thiago Macieira
Post by Nicol Bolas
Post by Thiago Macieira
They're against making the ugly parameters currently used for various reasons
become API.
But as has been previously stated, the standard doesn't define those
parameter names. So they wouldn't be able to become part of the API.
They
Post by Nicol Bolas
would be implementation defined, rather than standard defined.
The standard doesn't define, but all implementations have parameter names for
at least every parameter that is actually used in inline implementations. If
the proposal for designated initialisers will not require an a specialised
syntax for parameter names, then the existing, ugly names will be available
for library users.
I'm going to pretend you said "named parameters" instead of "designated
initializers", since they're *two different features*, no matter how much
you wish to conflate them.

Implementations often export symbols that they don't intend for users to
use. You can find things in `std::__detail` namespaces or whatever that you
could start making your program rely on.

That doesn't make it standard-protected behavior for you to rely on them.

If a user starts using parameter names that are not defined by the
standard, then it's no different from them poking at things in the
`std::__detail` namespace. In both cases, they are relying on
implementation-defined behavior.
Post by Thiago Macieira
Post by Nicol Bolas
And I'd point out that that nobody seems to be willing to stop concepts
from getting into C++17 just because it hasn't been applied to the
standard
Post by Nicol Bolas
library.
That's only analogous to the case where designated initialisers for parameter
names requires a new syntax: until the library implementations "opt in" to the
parameter names, they can't be used.
If the names aren't defined by the standard, then you're not allowed to
rely on them, per the above.
Post by Thiago Macieira
Post by Nicol Bolas
But then again, the whole named parameters discussion is off-topic: this
is
Post by Nicol Bolas
about designated initializers, which should not be conflated with named
parameters. After all, structure member names are already part of a
struct's API.
I consider this very much on topic.
I can't stop you from considering it whatever you like. But as far as
designated initializers as a feature is concerned, it is only about
aggregate initialization. It has nothing to do with function parameter
names and named parameters.
Post by Thiago Macieira
In C++, most useful aggregates have
constructors
No useful aggregates with constructors exist because, by C++'s definition,
an aggregate cannot have a constructor
<http://en.cppreference.com/w/cpp/language/aggregate_initialization>.

However, I'll pretend you used the right terminology there (classes, not
aggregates). The Core C++ Guidelines seem to disagree with you.
Constructors exist to protect invariants. And not all compilations of types
need invariants. Indeed, the inference from the guidelines is that you are
intended to write types that have the minimal invariant possible. Which
means that, if you have a type that contains two types, but doesn't have
any invariant between them, then what you have is a struct that has two
types.

An aggregate.
Post by Thiago Macieira
and unless we come up with a way for designating parameters to
constructors, designated initialisers will have a somewhat limited use for us.
Just because it doesn't solve every problem doesn't mean we shouldn't have
it. It solve actual, useful problems for people as is.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Thiago Macieira
2015-11-24 22:15:39 UTC
Permalink
Post by Nicol Bolas
I'm going to pretend you said "named parameters" instead of "designated
initializers", since they're *two different features*, no matter how much
you wish to conflate them.
Yes, I used the wrong term there.
Post by Nicol Bolas
Implementations often export symbols that they don't intend for users to
use. You can find things in `std::__detail` namespaces or whatever that you
could start making your program rely on.
That doesn't make it standard-protected behavior for you to rely on them.
I agree with you that people shouldn't rely on them, but it may still be the
fear of library developers that users may start depending on unspecified parts
of the API.
Post by Nicol Bolas
Post by Thiago Macieira
In C++, most useful aggregates have
constructors
No useful aggregates with constructors exist because, by C++'s definition,
an aggregate cannot have a constructor
<http://en.cppreference.com/w/cpp/language/aggregate_initialization>.
Sorry, I misused the term. I wanted to use something that encompassed both
"class" and "struct", so I opted for "aggregate", which does have the meaning
I meant in C and includes the other type of designated initialisers we're
talking about. In C11 6.2.5 Types, paragraph 21:

Arithmetic types and pointer types are collectively called scalar types.
Array and structure types are collectively called aggregate types.

Obviously C doesn't need the distinction that dcl.init.aggr p1 makes, since C
has no constructor or access level. I had just never realised C++ had the
distinction.
Post by Nicol Bolas
However, I'll pretend you used the right terminology there (classes, not
aggregates). The Core C++ Guidelines seem to disagree with you.
Constructors exist to protect invariants.
The guidelines are irrelevant to me, since they do not apply to the library I
mostly work on.
Post by Nicol Bolas
Post by Thiago Macieira
and unless we come up with a way for designating parameters to
constructors, designated initialisers will have a somewhat limited use for us.
Just because it doesn't solve every problem doesn't mean we shouldn't have
it. It solve actual, useful problems for people as is.
Not disputing that it solves problems. I'm saying simply that I wish we went
further and enabled a whole set of new and more common uses.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Ville Voutilainen
2015-11-24 20:05:39 UTC
Permalink
And I'd point out that that nobody seems to be willing to stop concepts from
getting into C++17 just because it hasn't been applied to the standard
library.
You have a funny definition of "nobody". It seems a definition so anecdotal that
it defeats the meaning of the word.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Hannes Landeholm
2015-11-23 19:14:20 UTC
Permalink
Post by Nicol Bolas
There are three areas of concern when it comes to designated initializers
in C++, which have to do with C++-specific features.
The first is base class member initialization. I think the best way to
{ .base_class = {.mem1 = 5, .mem2 = {init}}, .outer1 = {1, 2, 3} };
That is, each class level gets its own initializer list. I think this is
cleaner than doing `.base_class.mem1`, and it avoids the issue of classes
in the hierarchy with the same name. It also makes it clear that you're
setting members in a subobject.
Oh, and it allows you to initialize subobjects of the main class as you
see fit. We could have initialized `.base_class` without naming its
members, for example.
There is a downside to this approach. That downside is due to the second
issue: how designated initializers interact with non-designated
initializers in the same braced-init-list.
{3, "string", .member = 12};
How does that work? Do we initialize all of the designated members first,
then order the rest and initialize them if they were not initialized? How
does that interact with base class members?
I don't know much about designated initializers in C, so how do they
handle it?
Per http://www.compsci.hunter.cuny.edu/~sweiss/resources/c11standard.pdf,
6.7.9 Initialization:

17 Each brace-enclosed initializer list has an associated current object.
Post by Nicol Bolas
When no designations are present, subobjects of the current object are
initialized in order according to the type of the current object: array
elements in increasing subscript order, structure members in declaration
order, and the first named member of a union.148) In contrast, a
designation causes the following initializer to begin initialization of the
subobject described by the designator. Initialization then continues
forward in order, beginning with the next subobject after that described by
the designator.149)
18 Each designator list begins its description with the current object
associated with the closest surrounding brace pair. Each item in the
designator list (in order) specifies a particular member of its current
object and changes the current object for the next designator (if any) to
be that member.150) The current object that results at the end of the
designator list is the subobject to be initialized by the following
initializer.
19 The initialization shall occur in initializer list order, each
initializer provided for a particular subobject overriding any previously
listed initializer for the same subobject;151) all subobjects that are not
initialized explicitly shall be initialized implicitly the same as objects
that have static storage duration.
Essentially C has a "current object" that starts at the first declared
member of a struct or the first named member of a union. The "current
object" is then incremented forward for each non-designated item in the
initialization list. In contrast, a designated item overrides that "current
object". If this cases a member to be initialized twice it is well defined
per (19) that the later initializer overrides the previous one even though
a later rule states that the evaluation order of the statements are
undefined (23).

However compilers prints a warning if you are overriding previously
initialized fields (clang: -Winitializer-overrides) so this is likely
frowned upon even if it's well defined behaviour. I don't see any benefit
in having C++ support this.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Nicol Bolas
2015-11-23 20:33:58 UTC
Permalink
Post by Hannes Landeholm
Post by Nicol Bolas
There are three areas of concern when it comes to designated initializers
in C++, which have to do with C++-specific features.
The first is base class member initialization. I think the best way to
{ .base_class = {.mem1 = 5, .mem2 = {init}}, .outer1 = {1, 2, 3} };
That is, each class level gets its own initializer list. I think this is
cleaner than doing `.base_class.mem1`, and it avoids the issue of classes
in the hierarchy with the same name. It also makes it clear that you're
setting members in a subobject.
Oh, and it allows you to initialize subobjects of the main class as you
see fit. We could have initialized `.base_class` without naming its
members, for example.
There is a downside to this approach. That downside is due to the second
issue: how designated initializers interact with non-designated
initializers in the same braced-init-list.
{3, "string", .member = 12};
How does that work? Do we initialize all of the designated members first,
then order the rest and initialize them if they were not initialized? How
does that interact with base class members?
I don't know much about designated initializers in C, so how do they
handle it?
Per http://www.compsci.hunter.cuny.edu/~sweiss/resources/c11standard.pdf,
17 Each brace-enclosed initializer list has an associated current object.
Post by Nicol Bolas
When no designations are present, subobjects of the current object are
initialized in order according to the type of the current object: array
elements in increasing subscript order, structure members in declaration
order, and the first named member of a union.148) In contrast, a
designation causes the following initializer to begin initialization of the
subobject described by the designator. Initialization then continues
forward in order, beginning with the next subobject after that described by
the designator.149)
18 Each designator list begins its description with the current object
associated with the closest surrounding brace pair. Each item in the
designator list (in order) specifies a particular member of its current
object and changes the current object for the next designator (if any) to
be that member.150) The current object that results at the end of the
designator list is the subobject to be initialized by the following
initializer.
19 The initialization shall occur in initializer list order, each
initializer provided for a particular subobject overriding any previously
listed initializer for the same subobject;151) all subobjects that are not
initialized explicitly shall be initialized implicitly the same as objects
that have static storage duration.
Essentially C has a "current object" that starts at the first declared
member of a struct or the first named member of a union. The "current
object" is then incremented forward for each non-designated item in the
initialization list. In contrast, a designated item overrides that "current
object". If this cases a member to be initialized twice it is well defined
per (19) that the later initializer overrides the previous one even though
a later rule states that the evaluation order of the statements are
undefined (23).
However compilers prints a warning if you are overriding previously
initialized fields (clang: -Winitializer-overrides) so this is likely
frowned upon even if it's well defined behaviour. I don't see any benefit
in having C++ support this.
C++ most assuredly should make multiple initializations to the same field
be an error. That being said, I like the idea of the "current object"
increment. It makes it clear how designated initializers work with normal
aggregate initialization, as well as allowing default member initializers
to still work if you jump around them.
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Thiago Macieira
2015-11-23 23:48:07 UTC
Permalink
Post by Hannes Landeholm
Essentially C has a "current object" that starts at the first declared
member of a struct or the first named member of a union. The "current
object" is then incremented forward for each non-designated item in the
initialization list. In contrast, a designated item overrides that "current
object". If this cases a member to be initialized twice it is well defined
per (19) that the later initializer overrides the previous one even though
a later rule states that the evaluation order of the statements are
undefined (23).
That means in the following:

struct S { int i; };
struct S s = { .i = f(1), .i = f(2) };

It is undefined in which order the function f() will be called, but it will be
called twice and the value of s.i will be the result of f(2).

Since all types in C are POD, double assignment can be eliminated.

I believe it follows that using the object being initialised in the
initialiser list is undefined behaviour. That is:

struct T { int i, j; };
struct T s = { 1, f(s.i) };

Though I can't actually put my finger on what the cause of UB is. It could be
just reading from an uninitialised variable, which is just implementation-
defined behaviour up until C++14.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358
--
---
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+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Loading...