I agree anonymous structs would be a great feature.[1] Would there be less risk of ambiguous parses if the designated initialiser syntax of C was taken - and would that make it easier to retrofit into C?void display(Screen& target, struct { int xres, int yres/*, etc..*/ }); //<- compiler generated anonymous struct typedisplay(screen, { .yres=480, .xres=640});
[2] Would it also be a good way of doing multiple return values. Might actually be more pleasant than tuples in other languages.auto do_something()-> struct { int x; int y }
[3] could it work with auto...auto do_something() {return {.x=..., .y=....}}
That also implies adding support for C tagged structure initialization to C++, which implies defining how it works with constructors. At first pass, I'd say only enable the syntax for POD structs. This would need to be studied very carefully. I wouldn't be surprised if this was already brought up in the past, studied, and rejected for some reason. Does anyone have insight on that? Google doesn't appear to be helpful.
> What we're essentially doing is adding a compiler generated default
> constructor which takes a set of arguments equal to the data members. This
> does not make sense at all unless all data members are public.
Correct, but that doesn't change brace initialisation. You can't do that for
non-public members:
>
> struct S {
> int x;
> int y;
> S(int x, int y) = default;
> }
> How do we "default" all of the lvalue/rvalue variations?
Hmm... that's a good question. Maybe if you default the default constructor,
it should just work. GCC, Clang and ICC appear to already work like that.
> What about inheritance? The below seems reasonable:
>
> struct A {
> int a;
> int b;
> };
>
> struct B : public A {
> int c;
> int d;
> };
>
> B b{ .a = 1, .b = 2, .c =3, .d = 4};
This does not exist in C, so we're in new territory here. In C++98, B wasn't
eligible for brace initialisation because it wasn't POD. With C++11, it's now
trivial layout and trivially initialisable, but it's still not brace-
initialisable.
I don't see a need to change. You must provide a constructor if you want the
above to work:
constexpr B(int a, int b, int c, int d) : A{a, b}, c{c}, d{d} {}
> How about copy initialization?
>
> B b = { a. =1 , b = .2, .c = 3, .d = 4};
Same rules as from pre-history: the copy constructor is elided and you just
call the default constructor above. It needs to be supported for C99
compatibility too.
> How about assignment?
>
> B b;
> b = { a. = 1, ... };
This is a different one. It probably should not work as written. But this
should:
B b;
b = B{ .a = 1 };
b = B(.a = 1);
> If the {} expression produces an anonymous struct, that means we need some
> kind of implicit conversion to B. How does this affect C compatibility?
>
> How about unions?
You can initialise at most one element. Right now, for:
union U { int i; double d; };
The following works:
U u1 = {};
U u2 = { 1 };
But this doesn't:
U u3 = { 1.0 };
due to narrowing of double to int, when trying to initialise U(int) [it works
in C++98 and in C, but produces possibly unexpected results]. This works in
C99:
union U u4 = { .d = 1.0 };
So we should allow in C++ too.
So the rule for unions is that the default
constructor is equivalent to one-argument constructors taking each of the
members, with the exact same access level, type and name.
The interesting thing here will be dealing with C++11's unrestricted unions:
the constructor gets deleted by default if any of the members has a non-
trivial constructor (e.g., add std::string s; to the union above). We can
bring them back:
union U {
int i;
std::string s;
constexpr U(int i) : i(i) {}
U(std::string s) : s(s) {}
~U() {}
};
Should there be a shortcut that allows us to get the per-element constructors?
By the way, if you write ~U() = default; the destructor is still deleted.
--
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
On 2014–06–17, at 5:43 AM, corentin....@cea.fr wrote:Le lundi 16 juin 2014 21:18:52 UTC+2, Matthew Fioravante a écrit :That also implies adding support for C tagged structure initialization to C++, which implies defining how it works with constructors. At first pass, I'd say only enable the syntax for POD structs. This would need to be studied very carefully. I wouldn't be surprised if this was already brought up in the past, studied, and rejected for some reason. Does anyone have insight on that? Google doesn't appear to be helpful.
A proposal has been written recently:
https://groups.google.com/a/isocpp.org/forum/#!msg/std-proposals/IgDFqKjKlRs/CGARpDJy9JsJ
I do not know what is the current status of that proposal though.It would probably be better to make a clean start, based on review of discussions on this forum.Note, I was advocating the designated initializer solution earlier in this thread but nobody seemed to care at the time. Anyone who’s interested might check those posts.
On 2014–06–17, at 9:37 AM, David Krauss <pot...@gmail.com> wrote:On 2014–06–17, at 5:43 AM, corentin....@cea.fr wrote:Le lundi 16 juin 2014 21:18:52 UTC+2, Matthew Fioravante a écrit :That also implies adding support for C tagged structure initialization to C++, which implies defining how it works with constructors. At first pass, I'd say only enable the syntax for POD structs. This would need to be studied very carefully. I wouldn't be surprised if this was already brought up in the past, studied, and rejected for some reason. Does anyone have insight on that? Google doesn't appear to be helpful.
A proposal has been written recently:
https://groups.google.com/a/isocpp.org/forum/#!msg/std-proposals/IgDFqKjKlRs/CGARpDJy9JsJ
I do not know what is the current status of that proposal though.It would probably be better to make a clean start, based on review of discussions on this forum.Note, I was advocating the designated initializer solution earlier in this thread but nobody seemed to care at the time. Anyone who’s interested might check those posts.Oh, it was the other thread. And my strongest point was that, even if named parameters are implemented separately from pass-by-struct, folks will still combine designated initializers with pass-by-struct anyway. This results in two similar ways of doing the same thing, which should have similar if not same syntax.
This thread seems to be simply taking the alternative idea and running with it. :)One suggestion, but it’s only that: the struct is the sole argument, then allow designated initializer syntax directly in the argument list — i.e., allow brace elision.struct rect {int top, left, bottom, right;rect( /* struct */ { int in_top, in_left, in_bottom, in_right; } ): top( in_top ), left( in_left ), bottom( in_bottom ), right( in_right ) {}rect( /* struct */ { int in_top, in_left, in_height, in_width; } ): top( in_top ), left( in_left ), bottom( in_top + in_width ), right( in_left + in_width ) {}};rect a( .top = 4, .height = 43, .left = 10, .width = 100 );rect b( .top = 4, .bottom = 47, .left = 10, .right = 110 );
The original style:int foo(int a, int b, string c);foo(.a = 0, .b = 1, .c = "Hello");Pros:- Very simple- No ABI changes- All legacy code immediately becomes named function enabledCons:
- Cannot use named syntax when calling via a function pointer, since the type has no concept of parameter names.- Requires some language changes to enable the callsite syntax- Argument names while not part of the ABI, are now part of the API. You cannot change the names or you will break callers.
- All legacy code immediately becomes named function enabled.
The struct approachint foo(int a, {int b; int c;}, }Pros:-Separates positional and named arguments. Is this desirable??
-No ABI changes again, just reuses the type system with anonymous structs-Function pointers can also be name initialized when called, since their type encodes the type of the struct, and the struct can be initialized using tagged init syntax. This sounds like a big win.-No special rules, all syntax inherited from struct
Cons;-Functions with same arguments still have different types.
-Requires anonymous structs proposal
-If you have 2 overloads, with same positional arguments and different structs, how do you choose which one to pick from initializer syntax?void foo(int a, {int b; int c;});void foo(int a, {string b="", string c=""});foo(1, {.c = "hello" });I suppose the naming rule here could try to initialize each instance of foo using the brace expression in order of appearance in an SFINAE like manner.
> I’d also help draft such a proposal if someone wants a collaborator. Right now I’m hoping to attend the November meeting > in Champaign, although it’s not a guarantee and I might have a full plate with other proposals. However, at this point a> paper exploring the design space might be more appropriate than an attempt at fully-baked wording, which reduces the> burden of advocacy. There are a number of issues and corner cases to check, and the committee needs to approve the> “what” before the “how.”Does it make sense to cram all of these sort of unrelated things into one big proposal? Or do it incrementally with multiple proposals with the big picture in mind?
I could start writing one or help with writing one and fleshing out the details. It would be great to have someone actually present it at the meetings, since I don't actually have the ability to attend myself.
Em ter 17 jun 2014, às 12:43:19, David Krauss escreveu:
> On 2014-06-17, at 10:46 AM, Matthew Fioravante <fmatth...@gmail.com>
wrote:
> > The original style:
> >
> > int foo(int a, int b, string c);
> > foo(.a = 0, .b = 1, .c = "Hello");
> >
> > Pros:
> > - Very simple
> > - No ABI changes
> > - All legacy code immediately becomes named function enabled
>
> > Cons:
> The big con is that without ABI support, there's no overloading. My example
> wouldn't work because both rect::rect overloads would mangle to the same
> name.
Use a tag struct as a differentiator:
struct TopLeftBottomRight {};
rect(int top, int left, int bottom, int right, TopLeftBottomRight = {})
struct TopLeftWidthHeight {};
rect(int top, int left, int width, int height, TopLeftWidthHeight = {});
> > - Cannot use named syntax when calling via a function pointer, since the
> > type has no concept of parameter names. - Requires some language changes
> > to enable the callsite syntax
> > - Argument names while not part of the ABI, are now part of the API. You
> > cannot change the names or you will break callers.
> This is sort-of inherent to the feature, but it suggests that having foreign
> (member declaration) syntax for named parameters is actually a good thing.
You can specify parameter names in the function pointer syntax...
struct point {int top, left;};struct rect {
point top_left, bottom_right;float angle;
rect( /* struct */ { int top, left, bottom, right; }, float in_angle = 0 )
: top( top ), left( left ), bottom( bottom ), right( right ), angle( in_angle ) {}rect( /* struct */ { int top, left, height, width; }, float in_angle = 0 )
: top( in_top ), left( in_left )
, bottom( in_top + in_width ), right( in_left + in_width ), angle( in_angle ) {}
rect( /* struct */ { point top_left, point bottom_right; }, float in_angle = 0 )
: top_left( top_left ), bottom_right( bottom_right ), angle( in_angle ) {}
};
rect a( .top = 4, .height = 43, .left = 10, .width = 100 ); // OK
rect b( .top_left = { 4, 43 }, .bottom_right = { 10, 100 } ); // OK
rect c( .top_left = 4, 43, .bottom_right = 10, 100 ); // OK: (additional) brace elision
As I'm working on the paper and thinking about the details of the proposal, I've found a few issues to wrestle with.
The major difference between aggregate initialization and a set of what I call in the paper compiler generated "aggregate member constructors":
Aggregates can be left in an uninitialized state by not specifying an initializer. If we drop aggregate initialization in favor of constructors, we still need to support this behavior.
First, this syntax is yet another special case inherited for C compatibility. These compatibility cases complicate the standard and the learning curve for C++ developers. It would be better if this feature could be reimplemented using already existing C++ rules and mechanisms and still retain C compatibility.
Named function parameters using structs chicken and egg problem:
If we had named function params, aka:
int foo(int a, int b, int c);
foo(.a = 1, .b = 2, .c = 4);
Then having designated initializers for structs would just fall out from this in the constructors.
If we introduce designated initializers first, then named function parameters are derived from that. I'm falling more and more into the second camp because as I think about it, separating positional and named arguments seems to make a lot of sense.
On 2014–06–17, at 10:59 PM, Matthew Fioravante <fmatth...@gmail.com> wrote:As I'm working on the paper and thinking about the details of the proposal, I've found a few issues to wrestle with.
The major difference between aggregate initialization and a set of what I call in the paper compiler generated "aggregate member constructors":
Aggregates can be left in an uninitialized state by not specifying an initializer. If we drop aggregate initialization in favor of constructors, we still need to support this behavior.Who is “we”? Surely you don’t mean dropping it from the standard, but you’re just considering the case when the user is migrating a class from from aggregate to constructor?Hmm. Checking the draft:First, this syntax is yet another special case inherited for C compatibility. These compatibility cases complicate the standard and the learning curve for C++ developers. It would be better if this feature could be reimplemented using already existing C++ rules and mechanisms and still retain C compatibility.
This seems to be veering away from the problem statement. “Newer would be better” is not much of a motivation. You need to clearly state what’s broken before going down this path. For what it’s worth, aggregate initialization has been maintained just as carefully as the rest of the language.
Named function parameters using structs chicken and egg problem:
If we had named function params, aka:
int foo(int a, int b, int c);
foo(.a = 1, .b = 2, .c = 4);
Then having designated initializers for structs would just fall out from this in the constructors.
If we introduce designated initializers first, then named function parameters are derived from that. I'm falling more and more into the second camp because as I think about it, separating positional and named arguments seems to make a lot of sense.This seems to be the same as what you’ve been saying throughout this thread. Is there a problem or are you affirming the previous ideas? I’m a little confused. Also I don’t see how this relates to what’s in the posted draft so far.