Why does C++11 not support designated initializer lists as C99?

79,699

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 standard: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
This brings limited support for '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 and earlier Boost actually has support for Designated Intializers and there have been numerous proposals to add support to the standard, for example: n4172 and Daryle Walker's Proposal to Add Designation to Initializers. The proposals cite implementation of '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 : struct X foo = {.a = (char)f(), .b = g(), .c = h()}? Surprisingly, in :

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:)

  1. h()
  2. f()
  3. 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?

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 support would have required this to be executed in the order:

  1. f()
  2. g()
  3. h()

Breaking compatibility with previous implementations.
As discussed above, this issue has been circumvented by the limitations on Designated Initializers accepted into . 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.

Share:
79,699
xmllmx
Author by

xmllmx

Updated on January 27, 2022

Comments

  • xmllmx
    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 standard committee's rationale for excluding support for such a handy feature?

  • Johannes Schaub - litb
    Johannes Schaub - litb over 10 years
    Please 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
    Johannes Schaub - litb over 10 years
    Isn'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 write Person p = { 0, 0, 18 }; (and for good reasons).
  • Johannes Schaub - litb
    Johannes Schaub - litb over 10 years
    Something similar has recently been accepted into the C++14 spec by open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.html .
  • bames53
    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
    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
    Ben Voigt about 9 years
    Except 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
    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
    Ben Voigt about 9 years
    Sure, in this code: struct X { int c; char a; float b; }; X x = { .a = f(), .b = g(), .c = h() }; the call to h() is performed before either f() or g(). If the definition of struct 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
    Ben Voigt about 9 years
    Of 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
    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
    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
    Ben Voigt about 9 years
    aliasing 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
    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
    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
    Ben Voigt about 9 years
    Which makes (part of) the alleged rationale for the feature a lie.
  • dyp
    dyp almost 9 years
    Now 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
    Arvid almost 9 years
    I 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
    David Baird over 8 years
    The 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
    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
    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
    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
    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
    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-
    rr- over 8 years
    But 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
    Jonathan Mee over 8 years
    @rr- Was this proposal rejected?
  • rr-
    rr- over 8 years
    ...right, I confused it with N4172. This one didn't even get a document number.
  • Jonathan Mee
    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
    hedayat about 8 years
    Boost already provides named function parameters. IMHO, it's sad that C++ doesn't have this feature like C.
  • underscore_d
    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
    musiphil over 7 years
    If 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
    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 or struct sockaddr_in, so you're left with assignments separate from declarations.
  • dyp
    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
    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
    bames53 almost 7 years
    C#'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
    Ruslan over 6 years
    But 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.
    Sz. over 6 years
    Jonathan: "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
    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.
    Sz. about 6 years
    Ah, 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
    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
    TankorSmash almost 6 years
    Neat, creates a lambda with a variable named $ of type T, and you assign its members directly before returning it. Nifty. I wonder if there are any performance concerns with it.
  • keebus
    keebus almost 6 years
    In an optimized build you see no traces of the lambda nor its invocation. It's all inlined.
  • supercat
    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
    Stuntddude over 5 years
    A 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
    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
    Stuntddude over 5 years
    The 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
    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
    Seph Reed over 5 years
    I absolutely love this answer.
  • odougs
    odougs about 5 years
    Designated 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
    Chris Watts about 5 years
    Woah. Didn't even know $ was a valid name.
  • keebus
    keebus about 5 years
    It was supported by legacy C compilers and support stayed for backward compatibility.
  • ceztko
    ceztko over 4 years
    Designated initializers are coming in C++20
  • Neonit
    Neonit over 4 years
    So 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
    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
    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
    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)