¿Por qué no está std::initializer_list
incorporado 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_list
es 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)
- una matriz clásica, probablemente podría agregar
const
aquí y allá - ya existen tres puntos en el lenguaje (var-args, ahora plantillas variadic), ¿por qué no reutilizar la sintaxis (y hacer que se sienta incorporada )
- solo un contenedor existente, podría agregar
const
y&
Todos ellos ya forman parte del idioma. Solo escribí mis 3 primeras ideas, estoy seguro de que hay muchos otros enfoques.
fuente
std::array<T>
no es más "parte del lenguaje" questd::initializer_list<T>
. Y estos no son los únicos componentes de la biblioteca en los que se basa el lenguaje. Vernew
/delete
,type_info
, diversos tipos de excepción,size_t
, etc.const T(*)[N]
, porque se comporta de manera muy similar a cómostd::initializer_list
funciona.std::array
o una matriz de tamaño estático son alternativas menos deseables.Respuestas:
Ya había ejemplos de características de lenguaje "principales" que devolvían tipos definidos en el
std
espacio de nombres.typeid
regresastd::type_info
y (quizás estirando un punto)sizeof
regresastd::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:
std
lugar de como incorporados.Por lo tanto:
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.fuente
type_info
ysize_t
son buenos argumentos ... bueno,size_t
es solo un typedef ... así que saltemos esto. La diferencia entretype_info
yinitializer_list
es 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 esoinitializer_list
podría reemplazarse con algunos contenedores ya existentes ... o mejor aún: ¡cualquiera que el usuario declare como tipo de argumento!vector
eso toma un,array
entonces 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 cualquierarray
, pero no es la intención de la Comisión en la introducción de la nueva sintaxis.std::array
ni siquiera tiene constructores. Elstd::array
caso 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.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_list
como 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_list
contenedor como cualquier otro le brinda la oportunidad de escribir código genérico que funcione con cualquiera de esas cosas.ACTUALIZAR:
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_list
y todos los demás contenedores estándar (incluidosstd::array<>
) es que estos últimos tienen semántica de valor , mientras questd::initializer_list
tiene semántica de referencia . Copiar unstd::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.
fuente
std::array
. Perostd::array
asigna memoria mientrasstd::initializaer_list
envuelve una matriz en tiempo de compilación. Piense en ello como la diferencia entrechar s[] = "array";
ychar *s = "initializer_list";
.std::array
no asigna memoria, es simpleT arr[N];
, lo mismo que está respaldandostd::initializer_list
.T arr[N]
asigna memoria, tal vez no en el montón dinámico sino en otro lugar ... También lo hacestd::array
. Sin embargo,initializer_list
el usuario no puede construir un no vacío, por lo que obviamente no puede asignar memoria.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 lasome_container
clase. 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.fuente
begin
yend
métodos. Esto es un poco diferente en mi opinión.iterable class MyClass { };
initializer_list
aunqueDe 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 elstd
espacio de nombres tenga algunas construcciones de lenguaje central en su interior.fuente
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 aint i(5);
, oint i=5;
inclusointwrapper iw {5};
dóndeintwrapper
es un simple contenedor de clase sobre un int con un constructor trivial teniendo unainitializer_list
fuente
int i {5}
involucra a cualquierastd::initializer_list
es incorrecta.int
no tiene toma de constructorstd::initializer_list
, por lo que5
se usa directamente para construirlo. Entonces, el ejemplo principal es irrelevante; simplemente no hay optimización por hacer. Más allá de eso, dado questd::initializer_list
implica 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 resultanNo es parte del lenguaje principal porque se puede implementar por completo en la biblioteca, solo línea
operator new
yoperator delete
. ¿Qué ventaja habría en hacer que los compiladores fueran más complicados de construir?fuente