examples - ¿Por qué C++ 11 no admite listas de inicializadores designados como C99?
initialize char array java (5)
Boost en realidad tiene soporte para Inicializadores Designados y ha habido numerosas propuestas para agregar soporte al estándar c ++ , por ejemplo: n4172 y la Propuesta de Daryle Walker para Agregar Designación a Inicializadores . Las propuestas citan la implementación de los Inicializadores Designados de c99 en Visual C ++, gcc y Clang que afirman:
Creemos que los cambios serán relativamente sencillos de implementar
Pero el comité estándar rechaza repetidamente tales propuestas , declarando:
EWG encontró varios problemas con el enfoque propuesto, y no pensó que fuera factible intentar resolver el problema, ya que se ha intentado muchas veces y cada vez que ha fallado
Los comentarios de Ben Voigt me han ayudado a ver los problemas insuperables de este enfoque; dado:
struct X {
int c;
char a;
float b;
};
¿En qué orden se llamarían estas funciones en c99 : struct X foo = {.a = (char)f(), .b = g(), .c = h()}
? Sorprendentemente, en c99 :
El orden de evaluación de las subexpresiones en cualquier inicializador está secuenciado indefinidamente [ 1 ]
(Visual C ++, gcc y Clang parecen tener un comportamiento acordado, ya que todos harán las llamadas en este orden :)
-
h()
-
f()
-
g()
Pero la naturaleza indeterminada del estándar significa que si estas funciones tuvieran alguna interacción, el estado del programa resultante también sería indeterminado, y el compilador no lo advertiría : ¿Hay alguna forma de advertirse sobre el mal funcionamiento de los inicializadores designados?
c ++ tiene estrictos requisitos de lista de inicializadores 11.6.4 [dcl.init.list] 4:
Dentro de la lista de inicialización de una lista inicial arrinconada, las cláusulas inicializadoras, incluidas las que resultan de las expansiones de paquetes (17.5.3), se evalúan en el orden en que aparecen. Es decir, cada cálculo de valor y efecto secundario asociado con una cláusula de inicializador dada se secuencia antes de cada cálculo de valor y efecto secundario asociado con cualquier cláusula de inicializador que lo sigue en la lista separada por comas de la lista de inicializadores.
Así que el soporte de C ++ habría requerido que esto se ejecutara en el orden:
-
f()
-
g()
-
h()
Rompiendo compatibilidad con implementaciones anteriores de c99 . Lo que se necesita aquí es un comportamiento explícito que rija el orden de ejecución de los activadores designados. Una propuesta más prometedora para el Grupo de trabajo central es P0329R3 que propone la inicialización designada con las siguientes limitaciones:
[ source ]
Considerar:
struct Person
{
int height;
int weight;
int age;
};
int main()
{
Person p { .age = 18 };
}
El código anterior es legal en C99, pero no es legal en C ++ 11.
¿Cuál es la lógica de que C ++ 11 no es compatible con una característica tan útil?
C ++ tiene constructores. Si tiene sentido inicializar solo un miembro, entonces eso se puede expresar en el programa implementando un constructor apropiado. Este es el tipo de abstracción que C ++ promueve.
Por otro lado, la función de inicializadores designados se trata más de exponer y hacer que los miembros sean más fáciles de acceder directamente en el código del cliente. Esto lleva a cosas como tener una persona de 18 años (¿años?) Pero con altura y peso cero.
En otras palabras, los inicializadores designados admiten un estilo de programación donde las partes internas están expuestas y el cliente tiene flexibilidad para decidir cómo quiere usar el tipo.
C ++ está más interesado en poner la flexibilidad del lado del diseñador de un tipo, por lo que los diseñadores pueden hacer que sea fácil usar un tipo correcto y difícil de usar de forma incorrecta. Poner al diseñador en control de cómo se puede inicializar un tipo es parte de esto: el diseñador determina los constructores, los inicializadores en clase, etc.
El inicializador designado se incluye actualmente en el cuerpo de trabajo de C ++ 20: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf para que finalmente podamos verlos.
Un poco de hackeo, así que solo lo comparto por diversión.
#define with(T, ...)/
([&]{ T ${}; __VA_ARGS__; return $; }())
Y úsalo como:
MyFunction(with(Params,
$.Name = "Foo Bar",
$.Age = 18
));
que se expande a:
MyFunction(([&] {
Params ${};
$.Name = "Foo Bar", $.Age = 18;
return $;
}()));
Dos características de Core C99 que C ++ 11 Carece mencionan "Inicializadores designados y C ++".
Creo que el ''inicializador designado'' está relacionado con la optimización potencial. Aquí uso "gcc / g ++" 5.1 como ejemplo.
#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();
}
Sabíamos que en el momento de la compilación, a_point.x
es cero, por lo que podríamos esperar que foo
esté optimizado en una sola 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
está optimizado para imprimir x == 0
solamente.
Para la versión C ++,
#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();
}
Y esta es la salida del código de ensamblaje optimizado.
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
Podemos ver que a_point
no es realmente un valor constante de tiempo de compilación.