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

C++CC++11InitializationC99

C++ Problem Overview


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 [tag:c++11] standard committee's rationale for excluding support for such a handy feature?

C++ Solutions


Solution 1 - C++

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

  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: https://stackoverflow.com/q/34614308/2642059

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

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

Breaking compatibility with previous [tag:c99] implementations.
As discussed above, this issue has been circumvented by the limitations on Designated Initializers accepted into [tag:c++20]. They provide a standardized behavior, guaranteeing the execution order of Designated Initializers.

Solution 2 - C++

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 3 - C++

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 - C++

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 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionxmllmxView Question on Stackoverflow
Solution 1 - C++Jonathan MeeView Answer on Stackoverflow
Solution 2 - C++bames53View Answer on Stackoverflow
Solution 3 - C++keebusView Answer on Stackoverflow
Solution 4 - C++SergeyAView Answer on Stackoverflow
Solution 5 - C++wcyView Answer on Stackoverflow