Why does C++11 not support designated initializer lists as C99?
Solution 1
C++ has constructors. If it makes sense to initialize just one member then that can be expressed in the program by implementing an appropriate constructor. This is the sort of abstraction C++ promotes.
On the other hand the designated initializers feature is more about exposing and making members easy to access directly in client code. This leads to things like having a person of age 18 (years?) but with height and weight of zero.
In other words, designated initializers support a programming style where internals are exposed, and the client is given flexibility to decide how they want to use the type.
C++ is more interested in putting the flexibility on the side of the designer of a type instead, so designers can make it easy to use a type correctly and difficult to use incorrectly. Putting the designer in control of how a type can be initialized is part of this: the designer determines constructors, in-class initializers, etc.
Solution 2
On July 15 '17 P0329R4 was accepted into the c++20 standard: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
This brings limited support for c99's Designated Initializers. This limitation is described as follows by C.1.7[diff.decl].4, given:
struct A { int x, y; };
struct B { struct A a; };
The following Designated Initializations, which are valid in C, are restricted in C++:
-
struct A a = { .y = 1, .x = 2 }
is invalid in C++ because designators must appear in the declaration order of the data members -
int arr[3] = { [1] = 5 }
is invalid in C++ because array designated initialization is not supported -
struct B b = {.a.x = 0}
is invalid in C++ because designators cannot be nested -
struct A c = {.x = 1, 2}
is invalid in C++ because either all or none of the data members must be initialized by designators
For c++17 and earlier Boost actually has support for Designated Intializers and there have been numerous proposals to add support to the c++ standard, for example: n4172 and Daryle Walker's Proposal to Add Designation to Initializers. The proposals cite implementation of c99's Designated Initializers in Visual C++, gcc, and Clang claiming:
We believe the changes will be relatively straightforward to implement
But the standard committee repeatedly rejects such proposals, stating:
EWG found various problems with the proposed approach, and didn't think it's feasible to try solving the problem, as it has been tried many times and every time it has failed
Ben Voigt's comments have helped me to see the insurmountable problems with this approach; given:
struct X {
int c;
char a;
float b;
};
What order would these functions be called in in c99: struct X foo = {.a = (char)f(), .b = g(), .c = h()}
? Surprisingly, in c99:
The order of evaluation of the subexpressions in any initializer is indeterminately sequenced [1]
(Visual C++, gcc, and Clang seem to have an agreed upon behavior as they will all make the calls in this order:)
h()
f()
g()
But the indeterminate nature of the standard means that if these functions had any interaction the resulting program state would also be indeterminate, and the compiler wouldn't warn you: Is there a Way to Get Warned about Misbehaving Designated Initializers?
c++ does have stringent initializer-list requirements 11.6.4[dcl.init.list]4:
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (17.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.
So c++ support would have required this to be executed in the order:
f()
g()
h()
Breaking compatibility with previous c99 implementations.
As discussed above, this issue has been circumvented by the limitations on Designated Initializers accepted into c++20. They provide a standardized behavior, guaranteeing the execution order of Designated Initializers.
Solution 3
A bit of hackery, so just sharing for fun.
#define with(T, ...)\
([&]{ T ${}; __VA_ARGS__; return $; }())
And use it like:
MyFunction(with(Params,
$.Name = "Foo Bar",
$.Age = 18
));
which expands to:
MyFunction(([&] {
Params ${};
$.Name = "Foo Bar", $.Age = 18;
return $;
}()));
Solution 4
Designated initializer are currently included in C++20 body of work: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf so we might finally see them!
Solution 5
Two Core C99 Features that C++11 Lacks mentions “Designated Initializers and C++”.
I think the ‘designated initializer’ related with potential optimization. Here I use “gcc/g++” 5.1 as an example.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
int x;
int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
if(a_point.x == 0){
printf("x == 0");
return 0;
}else{
printf("x == 1");
return 1;
}
}
int main(int argc, char *argv[])
{
return foo();
}
We knew at compilation time, a_point.x
is zero, so we could expected that foo
is optimized into a single printf
.
$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
0x00000000004004f0 <+0>: sub $0x8,%rsp
0x00000000004004f4 <+4>: mov $0x4005bc,%edi
0x00000000004004f9 <+9>: xor %eax,%eax
0x00000000004004fb <+11>: callq 0x4003a0 <printf@plt>
0x0000000000400500 <+16>: xor %eax,%eax
0x0000000000400502 <+18>: add $0x8,%rsp
0x0000000000400506 <+22>: retq
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc: "x == 0"
foo
is optimized to print x == 0
only.
For C++ version,
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
point(int _x,int _y):x(_x),y(_y){}
int x;
int y;
};
const struct point a_point(0,0);
int foo() {
if(a_point.x == 0){
printf("x == 0");
return 0;
}else{
printf("x == 1");
return 1;
}
}
int main(int argc, char *argv[])
{
return foo();
}
And this is output of the optimized assemble code.
g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>: push %rbx
0x00000000004005c1 <+1>: mov 0x200489(%rip),%ebx # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>: test %ebx,%ebx
0x00000000004005c9 <+9>: je 0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>: mov $0x1,%ebx
0x00000000004005d0 <+16>: mov $0x4006a3,%edi
0x00000000004005d5 <+21>: xor %eax,%eax
0x00000000004005d7 <+23>: callq 0x400460 <printf@plt>
0x00000000004005dc <+28>: mov %ebx,%eax
0x00000000004005de <+30>: pop %rbx
0x00000000004005df <+31>: retq
0x00000000004005e0 <+32>: mov $0x40069c,%edi
0x00000000004005e5 <+37>: xor %eax,%eax
0x00000000004005e7 <+39>: callq 0x400460 <printf@plt>
0x00000000004005ec <+44>: mov %ebx,%eax
0x00000000004005ee <+46>: pop %rbx
0x00000000004005ef <+47>: retq
We can see that a_point
is not really a compile time constant value.
xmllmx
Updated on January 27, 2022Comments
-
xmllmx over 2 years
Consider:
struct Person { int height; int weight; int age; }; int main() { Person p { .age = 18 }; }
The code above is legal in C99, but not legal in C++11.
What was the c++11 standard committee's rationale for excluding support for such a handy feature?
-
Johannes Schaub - litb over 10 yearsPlease show a reference link for what you say is the reason for C++ to not have designated initializers. I can't remember having ever seen the proposal for it.
-
Johannes Schaub - litb over 10 yearsIsn't the very reason of not providing a constructor for
Person
that its author wanted to provide the most possible flexibility for users to set and initialize the members? The user can also already writePerson p = { 0, 0, 18 };
(and for good reasons). -
Johannes Schaub - litb over 10 yearsSomething similar has recently been accepted into the C++14 spec by open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.html .
-
bames53 over 10 years@JohannesSchaub-litb I'm not talking about the purely mechanical, proximate cause (i.e., it hasn't been proposed to the committee). I'm describing what I believe to be the dominating factor. —
Person
has a very C design so C features may make sense. However C++ probably enables a better design which also obviates the need for designated initializers. — In my view removing the restriction on in-class initializers for aggregates is much more in line with the ethos of C++ than designated initializers. -
Abdurrahim over 9 years@JohannesSchaub-litb Then i ask you if weight covered with ifdef block and ignored. you have to know how many fields before age. And dont ever say it makes no sense; look linux kernel code, there are gazillion fields like that.
-
Ben Voigt about 9 yearsExcept it is not "order-independent" at all, the proposal evaluates initializer expressions in the lexical order of declaration of the members, not the order in which the initializers are designated. Talk about a design that causes surprising behavior!
-
Jonathan Mee about 9 years@BenVoigt Interesting, I hadn't read that in full when I posted the link. I'm not sure I understand how this would create a problem though, as I believe list initialization is illegal unless the object does not have a constructor, or has a constructor specifically written to take an
initializer_list
. In either case the first case the initialization order should make no difference, in the second case initialization is constructor controlled anyway, so I don't see a problem there either. Clearly you understand something I don't though, so could you elaborate further on, "surprising behavior"? -
Ben Voigt about 9 yearsSure, in this code:
struct X { int c; char a; float b; }; X x = { .a = f(), .b = g(), .c = h() };
the call toh()
is performed before eitherf()
org()
. If the definition ofstruct X
is not nearby, this is going to be very surprising. Remember that initializer expressions don't have to be side-effect free. -
Ben Voigt about 9 yearsOf course, this is nothing new, ctor member initialization already has this issue, but it's in the definition of a class member, so tight coupling is no surprise. And designated initializers can't reference the other members the way the ctor member-initializers can.
-
M.M about 9 years@BenVoigt it'd hardly be the only surprise in C++ initialization. It doesn't seem any more extreme than the order of execution of items in the ctor-initializer list not being executed in the order they appear.
-
Ben Voigt about 9 years@MattMcNabb: No, it's not more extreme. But one expects the developer implementing the class constructor to know the member declaration order. Whereas the consumer of the class might be a different programmer entirely. Since the whole point is to allow initialization without having to look up the order of members, this seems like a fatal flaw in the proposal. Since designated initializers can't reference the object being constructed, first impression is that initialization expressions could be evaluated first, in designation order, then member initialization in declaration order. But...
-
Ben Voigt about 9 yearsaliasing between parameters passed to the various member constructors potentially still causes problems. It just means that
X x{ y, std::move(y) };
is safe for a true aggregate, but not with a user-defined constructor, and not if designated initializers are brought into the mix. -
M.M about 9 years@BenVoigt as with things like
i = ++i;
the answer will be "technically case X is OK and case Y is UB, but Don't Do That." -
Jonathan Mee about 9 years@BenVoigt It seems that by virtue of the fact that the "consumer of the class" is directly naming and using the members that he must also be privy to the implementation order.
-
Ben Voigt about 9 yearsWhich makes (part of) the alleged rationale for the feature a lie.
-
dyp almost 9 yearsNow please try
constexpr point(int _x,int _y):x(_x),y(_y){}
. clang++'s optimizer seems to eliminate the comparison in your code as well. So, this is just a QoI issue. -
Arvid almost 9 yearsI would also expect the entire a_point object to be optimized away if it had internal linkage. i.e. put it in the anonymous namespace and see what happens. goo.gl/wNL0HC
-
David Baird over 8 yearsThe C++ replacement for this could be named function arguments. But as of right now, name arguments don't officially exist. See N4172 Named arguments for a proposal of this. It would make code less error prone and easier to read.
-
kriss over 8 years@BenVoigt: the initialisation order issue only matters when members are referencing each other. Why not just make the compiler emit a warning (or even an error) in this particular case. This leave open many use cases. Anyway if the initialisation order matters it has to be documented in the structure API because the user is not supposed to have any other access to structure definition (well, textual headers... but hopefully they will disappear someday).
-
Jonathan Mee over 8 years@kriss While I agree with your fervor to vindicate designated intialization, there is a problem beyond members referencing each other. In fact, it's an active problem in C99. I've updated my answer to help clarify this. In any case, I do agree with your thought. If the designers said this was an acceptable risk in C99, why not in C++99 too?
-
kriss over 8 years@Jonathan Mee: Yes, this is indeed an issue. The same problem also exists in C++ (for the same reason) when calling member initializers. Not worse than UB in expressions (this problem could be left as UB for initializer, why bother to define order?) It wouldn't worry me too much. The main use case I see for new C style initializers are initializers of library provided structures (static or not). The designated initialisers are the nearest thing I know of in C or C++ of python style named parameters (instead of order based ones).
-
Ben Voigt over 8 years@JonathanMee: Well, the other question answered that... C99 aggregate initializers are unordered, so there's no expectation for designated initializers to be ordered. C++ braced-init-lists ARE ordered, and the proposal for designated initializers uses a potentially-surprising order (you can't be consistent both with lexical order, used for all braced-init lists, and member order, used for ctor-initializer-lists)
-
Jonathan Mee over 8 years@BenVoigt Thanks for sticking with me while I work through this. I have updated the answer with a new-found understanding of what I believe you were communicating. I'd appreciate if you could certify this edit.
-
rr- over 8 yearsBut who cares about order? If you need to enforce some order, you use auxiliary variables for this, like to solve sequence point problems. Ehh why did it have to be rejected.
-
Jonathan Mee over 8 years@rr- Was this proposal rejected?
-
rr- over 8 years...right, I confused it with N4172. This one didn't even get a document number.
-
Jonathan Mee over 8 years@rr- Yeah that's what I was going to say I haven't seen a reference number for this one. Does that mean anything? I guess if it's almost two years old it means it's lost to time :( I'd still like to see a rationale for not accepting it. (Even though the rationale would probably be pretty similar to what I have in my answer.)
-
hedayat about 8 yearsBoost already provides named function parameters. IMHO, it's sad that C++ doesn't have this feature like C.
-
underscore_d about 8 years@DavidBaird I presume what you meant was that, with named arguments, one could declare a constructor with defaults for all arguments, then let the user choose which to override, via names. Is that right?
-
musiphil over 7 yearsIf we shouldn't have designated initializers so that we can put the designer of the type in control, we shouldn't have plain structs (without constructors), either, and only allow classes.
-
musiphil over 7 years@dyp: Even just defining a constructor is possible only if the type is under your control. You cannot do that, for example, for
struct addrinfo
orstruct sockaddr_in
, so you're left with assignments separate from declarations. -
dyp over 7 years@musiphil At least in C++14, those C-style structs can be properly set up in a constexpr function as local variables by using assignment, and then returned from that function. Additionally, my point was not to show an alternative implementation of the constructor in C++ which allows the optimization, but showcase that it is possible for the compiler to perform this optimization if the form of initialization is different. If the compiler is "good enough" (i.e. supports this form of optimization), then it should be irrelevant whether you use a ctor or designated initializers, or something else.
-
ceztko almost 7 years-1: C# has constructors and has designated initializer like initialization for struct/class. It's syntactic sugar and would be safe to add. Only reason for C++ not having this: someone voted against it in the committee
-
bames53 almost 7 yearsC#'s design motivations are different in a lot of ways from C++, so I don't see what that would have to do with this question. And again, the committee rejecting this change is merely the proximate cause. What's important is why the committee rejected it. It didn't reject it because it mistakenly thought the change would be "dangerous", so the fact that this feature is "safe" is irrelevant.
-
Ruslan over 6 yearsBut do note that they are restricted: In C++, designated initialization support is restricted compared to the corresponding functionality in C. In C++, designators for non-static data members must be specified in declaration order, designators for array elements and nested designators are not supported, and designated and non-designated initializers cannot be mixed in the same initializer list. This means that in particular, you still won't be able to easily make a an enum-keyed lookup table.
-
Sz. over 6 yearsJonathan: "c++ support would have required this to be executed in the order [...] Breaking compatibility with previous c99 implementations." I don't get this one, sorry. 1. If the order is indeterminate in C99, then obviously any actual order should be fine, including any arbitrary C++ choice. b) Not supporting the des. initializers at all kinda already breaks C99 compatibility even more...
-
Jonathan Mee over 6 years@Sz. C++ doesn't draw from the C99 standard. So there isn't compatibility there to start with. The comment is intended as, "If you take code that was compiled against C99, and tried to implement designated initializer lists as C++ would require, the behavior would differ."
-
Sz. about 6 yearsAh, OK then, thanks. (I know they are unrelated, I just tried to make the most sense of your compatibility note.) Anyhow, designated initializers are likely just a matter of time for C++ to finally pick up. (Alas, probably too much time, as it has become the norm with the evolution of the language.)
-
Jonathan Mee about 6 years@Sz. There was a lot more I could have dreamed of in C++17, this feature included... but I'm just so grateful the language is still growing I struggle to complain overall. There was just a lot of time before C++11, where the language I loved seemed to be dying. At this point... yeah, I'm just grateful.
-
TankorSmash almost 6 yearsNeat, creates a lambda with a variable named
$
of typeT
, and you assign its members directly before returning it. Nifty. I wonder if there are any performance concerns with it. -
keebus almost 6 yearsIn an optimized build you see no traces of the lambda nor its invocation. It's all inlined.
-
supercat over 5 years@Ruslan: I wonder why C++ restricted them so much? I understand that there could be confusion about whether the order in which items's values are evaluated and/or written to the struct matches the order in which items are specified in the initalization list, or the order in which members appear in the struct, but the solution to that would simply be to say that initialization expressions are executed in arbitrary sequence, and the lifetime of the object does not begin until initialization is complete (the
&
operator would return the address that the object will have during its lifetime). -
Stuntddude over 5 yearsA simple restriction of implementation-defined edge case behavior (from another language which is already incompatible!) is what the C++ community considers to be "insurmountable"? No wonder the language is such a mess.
-
Jonathan Mee over 5 years@Stuntddude There are 2 schools of thought on this, right? On one hand backwards comparability is valuable. On the other the evolution of the language to apply to today's problems is valuable. One of the things that has made C++ a successful standard is that it has navigated the intersections of these 2 goods with great care. In any case it sounds like we'd both agree that the standard committee has made the right decision here.
-
Stuntddude over 5 yearsThe right decision? They waited 20 years and then introduced a version of designated initialization so crippled it's nearly useless, largely killing the hope of proper designated initializers making it into the language. Now it's just another burden on compiler authors rather than something to get excited about.
-
Jonathan Mee over 5 years@Stuntddude I was actually really excited about this :/ I felt it gave me more than enough tools to do what I wanted, and on top of that it wasn't implementation defined, meaning that I have guaranteed behavior on any compliant compiler. All the shortcomings I could think of had simple workarounds, perhaps you have an example of a shortcoming that lacks a simple workaround?
-
Seph Reed over 5 yearsI absolutely love this answer.
-
odougs about 5 yearsDesignated initializers make initializing a large const struct much cleaner. This comes up, for example, if your class has an instance of a configuration struct, and also static const instances of the struct that define the maximum and minimum allowed configuration values. If the config is a list of a dozen floats and a few ints, the constructor gets messy. Splitting it into multiple sub-objects is not always the right abstraction. You can work around this with const_cast and storing a const pointer to the struct, but why should C++ make this harder than it is in C?
-
Chris Watts about 5 yearsWoah. Didn't even know $ was a valid name.
-
keebus about 5 yearsIt was supported by legacy C compilers and support stayed for backward compatibility.
-
ceztko over 4 yearsDesignated initializers are coming in C++20
-
Neonit over 4 yearsSo they needed 21 years to reintroduce a featue that was there before (my g++ supports it anyway, but VS complains) and even dare to still require us to provide fields in the declaration order? Btw. we do not want, because internals are exposed is an invalid argument, because having to look up the declaration order of fields is a heavier internals exposure (and also error-prone in case of insertions of new fields) than just throwing in some name-value pairs. Looks to me if there are people in the commitee that shouldn't be there.
-
mtraceur over 4 years@JonathanMee Maybe errnoname (the array-based optimization variant, not the
switch
-based default variant) is an example of something hard to do without out-of-order designated initializers for arrays? Or does that have a simple workaround too? (If so I'll post a separate question.) -
mtraceur over 4 years@JonathanMee Actually a link to the whole repo might be confusing - I mean the array-building code that starts here. Note the fact that there are no guarantees available about numerical value, not even relative ordering thereof, and the values are not available until compile (pre-processing) time.
-
Maliafo over 4 years@Neonit Designated initializers are supported in C since C99, C++ branched from C before that standard, so it is not reintroduction. That feature has never been part of C++ (prior C++20), nor was it removed from the "C/C++ branching point" (hopefully that makes sense)