¿Por qué std :: initializer_list no es un idioma integrado?

95

¿Por qué no está std::initializer_listincorporado un lenguaje central?

Me parece que es una característica bastante importante de C ++ 11 y, sin embargo, no tiene su propia palabra clave reservada (o algo similar).

En cambio, initializer_listes solo una clase de plantilla de la biblioteca estándar que tiene un mapeo implícito especial de la nueva sintaxis de lista de inicio con {...} llaves que maneja el compilador.

A primera vista, esta solución es bastante peligrosa .

¿Es esta la forma en que se implementarán ahora las nuevas adiciones al lenguaje C ++: por roles implícitos de algunas clases de plantilla y no por el lenguaje central ?


Considere estos ejemplos:

   widget<int> w = {1,2,3}; //this is how we want to use a class

por qué se eligió una nueva clase:

   widget( std::initializer_list<T> init )

en lugar de usar algo similar a cualquiera de estas ideas:

   widget( T[] init, int length )  // (1)
   widget( T... init )             // (2)
   widget( std::vector<T> init )   // (3)
  1. una matriz clásica, probablemente podría agregar constaquí y allá
  2. ya existen tres puntos en el lenguaje (var-args, ahora plantillas variadic), ¿por qué no reutilizar la sintaxis (y hacer que se sienta incorporada )
  3. solo un contenedor existente, podría agregar consty&

Todos ellos ya forman parte del idioma. Solo escribí mis 3 primeras ideas, estoy seguro de que hay muchos otros enfoques.

emesx
fuente
26
¡El comité de estándares odia agregar nuevas palabras clave!
Alex Chamberlain
11
Esto lo entiendo, pero hay muchas posibilidades de cómo extender el lenguaje (la palabra clave fue solo un ejemplo )
emesx
10
std::array<T>no es más "parte del lenguaje" que std::initializer_list<T>. Y estos no son los únicos componentes de la biblioteca en los que se basa el lenguaje. Ver new/ delete, type_info, diversos tipos de excepción, size_t, etc.
bames53
6
@Elmes: Yo habría sugerido const T(*)[N], porque se comporta de manera muy similar a cómo std::initializer_listfunciona.
Mooing Duck
1
Esto responde por qué std::arrayo una matriz de tamaño estático son alternativas menos deseables.
boycy

Respuestas:

48

Ya había ejemplos de características de lenguaje "principales" que devolvían tipos definidos en el stdespacio de nombres. typeidregresa std::type_infoy (quizás estirando un punto) sizeofregresa std::size_t.

En el primer caso, ya debe incluir un encabezado estándar para poder utilizar esta función denominada "lenguaje principal".

Ahora, para las listas de inicializadores, sucede que no se necesita ninguna palabra clave para generar el objeto, la sintaxis son llaves sensibles al contexto. Aparte de eso, es lo mismo que type_info. Personalmente, no creo que la ausencia de una palabra clave lo haga "más hacky". Un poco más sorprendente, quizás, pero recuerde que el objetivo era permitir la misma sintaxis de inicializador entre corchetes que ya estaba permitida para los agregados.

Entonces sí, probablemente pueda esperar más de este principio de diseño en el futuro:

  • si surgen más ocasiones en las que es posible introducir nuevas funciones sin nuevas palabras clave, el comité las tomará.
  • si las nuevas características requieren tipos complejos, esos tipos se colocarán en stdlugar de como incorporados.

Por lo tanto:

  • si una nueva función requiere un tipo complejo y puede introducirse sin nuevas palabras clave, obtendrá lo que tiene aquí, que es la sintaxis del "lenguaje principal" sin palabras clave nuevas y que utiliza tipos de biblioteca de std.

Creo que todo se reduce a que no existe una división absoluta en C ++ entre el "lenguaje central" y las bibliotecas estándar. Son capítulos diferentes en el estándar pero cada uno hace referencia al otro, y siempre ha sido así.

Hay otro enfoque en C ++ 11, que es que las lambdas introducen objetos que tienen tipos anónimos generados por el compilador. Debido a que no tienen nombres, no están en un espacio de nombres en absoluto, ciertamente no en std. Sin embargo, ese no es un enfoque adecuado para las listas de inicializadores, porque usa el nombre del tipo cuando escribe el constructor que acepta uno.

Steve Jessop
fuente
1
Me parece que esta división no es posible (¿mailny?) Debido a los roles implícitos de los tipos. type_infoy size_tson buenos argumentos ... bueno, size_tes solo un typedef ... así que saltemos esto. La diferencia entre type_infoy initializer_listes que el primero es el resultado de un operador explícito y el segundo de una acción implícita del compilador. También me parece que eso initializer_list podría reemplazarse con algunos contenedores ya existentes ... o mejor aún: ¡cualquiera que el usuario declare como tipo de argumento!
emesx
4
... o podría ser la simple razón de que si escribiste un constructor para vectoreso toma un, arrayentonces podrías construir un vector a partir de cualquier arreglo del tipo correcto, no solo uno generado por la sintaxis de la lista de inicializadores. No estoy seguro de que sería una mala cosa para construir los contenedores de cualquier array, pero no es la intención de la Comisión en la introducción de la nueva sintaxis.
Steve Jessop
2
@Christian: No, std::arrayni siquiera tiene constructores. El std::arraycaso es simplemente inicialización agregada. Además, les doy la bienvenida a unirse a mí en la sala de chat Lounge <C ++>, ya que esta discusión se está volviendo un poco larga.
Xeo
3
@ChristianRau: Xeo significa que los elementos se copian cuando se construye la lista de inicializadores. Copiar una lista de inicializadores no copia los elementos contenidos.
Mooing Duck
2
@La inicialización de la lista cristiana no implica initializer_list. Puede ser muchas cosas, incluida una buena inicialización directa o una inicialización agregada. Ninguno de ellos involucra initializer_list (y algunos simplemente no pueden funcionar de esa manera).
R. Martinho Fernandes
42

El Comité de Estándares C ++ parece preferir no agregar nuevas palabras clave, probablemente porque eso aumenta el riesgo de romper el código existente (el código heredado podría usar esa palabra clave como el nombre de una variable, una clase o cualquier otra cosa).

Además, me parece que definir std::initializer_listcomo contenedor con plantilla es una elección bastante elegante: si fuera una palabra clave, ¿cómo accedería a su tipo subyacente? ¿Cómo lo recorrerías? También necesitaría un montón de operadores nuevos, y eso solo lo obligaría a recordar más nombres y más palabras clave para hacer las mismas cosas que puede hacer con los contenedores estándar.

Tratar un std::initializer_listcontenedor como cualquier otro le brinda la oportunidad de escribir código genérico que funcione con cualquiera de esas cosas.

ACTUALIZAR:

Entonces, ¿por qué introducir un nuevo tipo en lugar de utilizar alguna combinación de los existentes? (de los comentarios)

Para empezar, todos los demás contenedores tienen métodos para agregar, eliminar y colocar elementos, que no son deseables para una colección generada por un compilador. La única excepción es std::array<>, que envuelve una matriz de estilo C de tamaño fijo y, por lo tanto, sería el único candidato razonable.

Sin embargo, como Nicol Bolas señala correctamente en los comentarios, otra diferencia fundamental entre std::initializer_listy todos los demás contenedores estándar (incluidos std::array<>) es que estos últimos tienen semántica de valor , mientras que std::initializer_listtiene semántica de referencia . Copiar un std::initializer_list, por ejemplo, no generará una copia de los elementos que contiene.

Además (una vez más, cortesía de Nicol Bolas), tener un contenedor especial para las listas de inicialización de llaves permite sobrecargar la forma en que el usuario realiza la inicialización.

Andy Prowl
fuente
4
Entonces, ¿por qué introducir un nuevo tipo en lugar de utilizar alguna combinación de los existentes?
emesx
3
@elmes: En realidad es más como std::array. Pero std::arrayasigna memoria mientras std::initializaer_listenvuelve una matriz en tiempo de compilación. Piense en ello como la diferencia entre char s[] = "array";y char *s = "initializer_list";.
rodrigo
2
Y al ser un tipo normal , la sobrecarga, la especialización de plantillas, la decoración de nombres y cosas por el estilo no son problemas.
rodrigo
2
@rodrigo: std::arrayno asigna memoria, es simple T arr[N];, lo mismo que está respaldando std::initializer_list.
Xeo
6
@Xeo: T arr[N] asigna memoria, tal vez no en el montón dinámico sino en otro lugar ... También lo hace std::array. Sin embargo, initializer_listel usuario no puede construir un no vacío, por lo que obviamente no puede asignar memoria.
rodrigo
6

Esto no es nada nuevo. Por ejemplo, se for (i : some_container)basa en la existencia de métodos específicos o funciones independientes en la some_containerclase. C # incluso confía aún más en sus bibliotecas .NET. En realidad, creo que esta es una solución bastante elegante, porque puede hacer que sus clases sean compatibles con algunas estructuras del lenguaje sin complicar la especificación del lenguaje.

Espectro
fuente
2
métodos en clase o independientes beginy endmétodos. Esto es un poco diferente en mi opinión.
emesx
3
¿Lo es? Nuevamente, tiene una construcción de lenguaje puro que depende de la construcción específica de su código. También podría haberse hecho mediante la introducción de una nueva palabra clave, por ejemplo,iterable class MyClass { };
Spook
pero puedes colocar los métodos donde quieras, implementarlos como quieras ... ¡Hay algo de similitud, estoy de acuerdo! Esta pregunta es sobre initializer_listaunque
emesx
4

De hecho, esto no es nada nuevo y cuántos han señalado, esta práctica estaba allí en C ++ y está allí, digamos, en C #.

Sin embargo, Andrei Alexandrescu ha mencionado un buen punto sobre esto: puede pensar en ello como parte del espacio de nombres "central" imaginario, entonces tendrá más sentido.

Por lo tanto, en realidad es algo así como: core::initializer_list, core::size_t, core::begin(), core::end()y así sucesivamente. Esta es solo una desafortunada coincidencia de que el stdespacio de nombres tenga algunas construcciones de lenguaje central en su interior.

Artem Tokmakov
fuente
2

No solo puede funcionar completamente en la biblioteca estándar. La inclusión en la biblioteca estándar no significa que el compilador no pueda realizar trucos ingeniosos.

Si bien es posible que no pueda hacerlo en todos los casos, es muy posible que diga: este tipo es bien conocido, o un tipo simple, ignoremos el initializer_list y solo tengamos una imagen de memoria de cuál debería ser el valor inicializado.

En otras palabras, int i {5};puede ser equivalente a int i(5);, o int i=5;incluso intwrapper iw {5};dónde intwrapperes un simple contenedor de clase sobre un int con un constructor trivial teniendo unainitializer_list

Paul de Vrieze
fuente
¿Tenemos ejemplos reproducibles de compiladores que realmente realizan "trucos ingeniosos" como este? Es un poco razonable razonar como si , pero me gustaría ver una justificación.
underscore_d
La idea de optimizar los compiladores es que el compilador puede transformar el código en cualquier código equivalente. C ++, en particular, se basa en la optimización de abstracciones "libres". La idea de reemplazar el código de la biblioteca estándar es común (mire la lista incorporada de gcc gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html ).
Paul de Vrieze
De hecho, tu idea que int i {5}involucra a cualquiera std::initializer_listes incorrecta. intno tiene toma de constructor std::initializer_list, por lo que 5se usa directamente para construirlo. Entonces, el ejemplo principal es irrelevante; simplemente no hay optimización por hacer. Más allá de eso, dado que std::initializer_listimplica que el compilador cree y haga proxy de una matriz 'imaginaria', supongo que puede favorecer la optimización, pero esa es la parte 'mágica' del compilador, por lo que es independiente de si el optimizador en general puede hacer algo inteligente con lo bonito objeto aburrido que contiene 2 iteradores que resultan
subrayado_d
1

No es parte del lenguaje principal porque se puede implementar por completo en la biblioteca, solo línea operator newy operator delete. ¿Qué ventaja habría en hacer que los compiladores fueran más complicados de construir?

Pete Becker
fuente