--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
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.
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 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.
On Saturday, November 21, 2015 at 9:11:37 AM UTC-5, land...@gmail.com wrote: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
... and?
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.
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.
On Saturday, November 21, 2015 at 9:11:37 AM UTC-5, land...@gmail.com wrote:
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)
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:
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 typesint x, y, z;}S s = {.x = 1, .y = 2, .z = 3};void f(S s);int main() { f({.x = 1, .y = 2, .z = 3}); }
On Sat, Nov 21, 2015 at 4:27 PM, Nicol Bolas <jmck...@gmail.com> wrote:On Saturday, November 21, 2015 at 9:11:37 AM UTC-5, land...@gmail.com wrote: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
... and?
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.
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's not the only point considered though.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.
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.
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 ;)
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 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.
On Sat, Nov 21, 2015 at 6:56 PM, Nicol Bolas <jmck...@gmail.com> wrote: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 only generated for classes without base classes.
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).
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.
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'm not sure what you mean by the "`vector<int>/initializer_list<int>` problem".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.
Designated initializers are nothing more than a special form of aggregate initialization.
And what if the base class has a member of the same name as a derived class member?
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 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 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.
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.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.
struct T
{
string str;
vector<float> vec;
};
T{.str = {"foo"}, .vec = {5, 0.3f}};
void func(std::designated_initiailzer_list foo)
{
T t(foo);
}
func({.str = {"foo"}, .vec = {5, 0.3f}});
func({.str = {"foo"}, .vect = {5, 0.3f}});
Or... we could avoid solving any of those problems and just make designated initialization a form of aggregate initialization.
{ .base_class = {.mem1 = 5, .mem2 = {init}}, .outer1 = {1, 2, 3} };
{3, "string", .member = 12};
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;
}
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;
}
On Monday, November 23, 2015 at 5:50:06 AM UTC-5, Hannes wrote:On Monday, November 23, 2015 at 3:54:27 AM UTC+1, Nicol Bolas wrote:
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:
On Monday, November 23, 2015 at 6:41:06 AM UTC-8, Nicol Bolas wrote:On Monday, November 23, 2015 at 5:50:06 AM UTC-5, Hannes wrote:On Monday, November 23, 2015 at 3:54:27 AM UTC+1, Nicol Bolas wrote:
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.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?
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.
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}};
SomeType t1 = {{5}}; //Calls single integer constructor.
SomeType t2 = {{.foo = 4, .bar = {1, 2, 3}}}; //Calls named parameter constructor.
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?
17 Each brace-enclosed initializer list has an associated current object. 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.
On Monday 23 November 2015 12:34:27 Nicol Bolas wrote:
> 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.
On Nov 23, 2015 7:47 PM, "Nicol Bolas" <jmck...@gmail.com> wrote:
>
> 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.
>
> --
>
> ---
> You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/875H9Elkhdw/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
On Nov 23, 2015 7:47 PM, "Nicol Bolas" <jmck...@gmail.com> wrote:
>
> 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.
On Monday 23 November 2015 20:47:47 Nicol Bolas wrote:
> 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.
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.
On Monday 23 November 2015 20:47:47 Nicol Bolas wrote:
> 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.
On Monday 23 November 2015 19:47:38 Nicol Bolas wrote:
> > 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.
> 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.
> 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.
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.
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.
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.
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.