argument - c++ initializer list
std:: initializer_list como argumento de funciĆ³n (5)
De manera manual, no estoy seguro, pero sospecho que lo que está sucediendo aquí es que la conversión a una initializer_list es una conversión, y convertir eso a vector es otra conversión. Si ese es el caso, está excediendo el límite de una sola conversión implícita ...
Por alguna razón, pensé que C ++ 0x permitía std::initializer_list
como argumento de función para funciones que esperan tipos que puedan construirse a partir de tales, por ejemplo std::vector
. Pero aparentemente, no funciona. ¿Es esto solo mi compilador, o esto nunca funcionará? ¿Es por posibles problemas de resolución de sobrecarga?
#include <string>
#include <vector>
void function(std::vector<std::string> vec)
{
}
int main()
{
// ok
std::vector<std::string> vec {"hello", "world", "test"};
// error: could not convert ''{"hello", "world", "test"}'' to ''std::vector...''
function( {"hello", "world", "test"} );
}
Debes especificar el tipo de tu initializer_list
function(std::initializer_list<std::string>{"hello", "world", "test"} );
Buena suerte
Esto es un error del compilador o su compilador no es compatible con std :: initializer_list. Probado en GCC 4.5.1 y compila bien.
GCC tiene un error. El Estándar lo hace válido. Ver:
Tenga en cuenta que hay dos lados de esto
- ¿Cómo y qué inicialización se hace en general?
- ¿Cómo se usa la inicialización durante la resolución de sobrecarga y qué costo tiene?
La primera pregunta se responde en la sección 8.5
. La segunda pregunta se responde en la sección 13.3
. Por ejemplo, el enlace de referencia se maneja en 8.5.3
y 13.3.3.1.4
, mientras que la inicialización de lista se maneja en 8.5.4
y 13.3.3.1.5
.
8.5/14,16
:
La inicialización que ocurre en la forma
T x = a;
así como en el paso de argumentos , el retorno de función, el lanzamiento de una excepción (15.1), el manejo de una excepción (15.3) y la inicialización de miembro agregado (8.5.1) se llama inicialización de copia.
.
.
La semántica de los inicializadores es la siguiente [...]: si el inicializador es una lista inicial arrinconada, el objeto se inicializa en la lista (8.5.4).
Al considerar la function
candidata, el compilador verá una lista de inicializadores (que todavía no tiene ningún tipo - ¡es solo una construcción gramatical!) Como el argumento, y un std::vector<std::string>
como el parámetro de la function
. Para saber cuál es el costo de conversión y si podemos convertirlos en el contexto de sobrecarga, 13.3.3.1/5
dice
13.3.3.1.5/1
:
Cuando un argumento es una lista de inicializadores (8.5.4), no es una expresión y se aplican reglas especiales para convertirlo a un tipo de parámetro.
13.3.3.1.5/3
:
De lo contrario, si el parámetro es una clase no agregada X y la resolución de sobrecarga por 13.3.1.7 elige un único mejor constructor de X para realizar la inicialización de un objeto de tipo X desde la lista de inicializadores de argumentos, la secuencia de conversión implícita es un usuario- secuencia de conversión definida. Las conversiones definidas por el usuario están permitidas para la conversión de los elementos de la lista de inicializadores a los tipos de parámetros de constructor, excepto como se indica en 13.3.3.1.
La clase no agregada X
es std::vector<std::string>
, y descubriré el mejor constructor individual a continuación. La última regla nos otorga el uso de conversiones definidas por el usuario en casos como los siguientes:
struct A { A(std::string); A(A const&); };
void f(A);
int main() { f({"hello"}); }
Estamos autorizados a convertir el literal de cadena a std::string
, incluso si esto necesita una conversión definida por el usuario. Sin embargo, apunta a restricciones de otro párrafo. ¿Qué dice 13.3.3.1
?
13.3.3.1/4
, que es el párrafo responsable de prohibir las conversiones definidas por el usuario múltiples. Solo veremos las inicializaciones de lista:
Sin embargo, cuando se considera el argumento de una función de conversión definida por el usuario [(o constructor)] que es candidata por [...] 13.3.1.7 al pasar la lista de inicializadores como un único argumento o cuando la lista de inicializadores tiene exactamente un elemento y se considera una conversión a alguna clase X o referencia a (posiblemente cv-quali fi cada) X para el primer parámetro de un constructor de X, o [...], solo se permiten secuencias de conversión estándar y secuencias de conversión de puntos suspensivos.
Observe que esta es una restricción importante: si no fuera por esto, lo anterior puede usar el constructor de copias para establecer una secuencia de conversión igualmente buena, y la inicialización sería ambigua. (observe la posible confusión de "A o B y C" en esa regla: significa "(A o B) y C", por lo que estamos restringidos solo cuando intentamos convertir por un constructor de X que tiene un parámetro de escriba X
).
Estamos delegados a 13.3.1.7
para recopilar los constructores que podemos usar para hacer esta conversión. Vamos a abordar este párrafo desde el lado general a partir de 8.5
que nos delegó a 8.5.4
:
8.5.4/1
:
La inicialización de la lista puede ocurrir en contextos de inicialización directa o inicialización de copia; La inicialización de lista en un contexto de inicialización directa se denomina inicialización de lista directa y la inicialización de lista en un contexto de inicialización de copia se llama copia-inicialización de lista .
8.5.4/2
:
Un constructor es un constructor de lista de inicializadores si su primer parámetro es de tipo
std::initializer_list<E>
o referencia a eventualmente cv-quali fi cadostd::initializer_list<E>
para algún tipo E, y o bien no hay otros parámetros o bien todos los demás parámetros tienen argumentos predeterminados (8.3.6).
8.5.4/3
:
La inicialización de lista de un objeto o referencia de tipo T se define de la siguiente manera: [...] De lo contrario, si T es un tipo de clase, se consideran los constructores. Si T tiene un constructor de lista de inicialización, la lista de argumentos consta de la lista de inicializadores como un único argumento; de lo contrario, la lista de argumentos consta de los elementos de la lista de inicializadores. Los constructores aplicables se enumeran (13.3.1.7) y el mejor se elige a través de la resolución de sobrecarga (13.3).
En este momento, T
es el tipo de clase std::vector<std::string>
. Tenemos un argumento (¡que todavía no tiene un tipo! Estamos en el contexto de tener una lista de inicializadores gramaticales). Los constructores se enumeran a partir de 13.3.1.7
:
[...] Si T tiene un constructor de listas de inicialización (8.5.4), la lista de argumentos consta de la lista de inicializadores como un único argumento; de lo contrario, la lista de argumentos consta de los elementos de la lista de inicializadores. Para la inicialización de lista de copia, las funciones candidatas son todos los constructores de T. Sin embargo, si se elige un constructor explícito, la inicialización está mal formada.
Solo consideraremos la lista de inicializadores de std::vector
como el único candidato, ya que sabemos que los demás no ganarán o no encajarán en el argumento. Tiene la siguiente firma:
vector(initializer_list<std::string>, const Allocator& = Allocator());
Ahora, las reglas de conversión de una lista de inicializadores a std::initializer_list<T>
(para categorizar el costo de la conversión argumento / parámetro) se enumeran en 13.3.3.1.5
:
Cuando un argumento es una lista de inicializadores (8.5.4), no es una expresión y se aplican reglas especiales para convertirlo a un tipo de parámetro. [...] Si el tipo de parámetro es
std::initializer_list<X>
y todos los elementos de la lista de inicializadores se pueden convertir implícitamente a X, la secuencia de conversión implícita es la peor conversión necesaria para convertir un elemento de la lista a X Esta conversión puede ser una conversión definida por el usuario incluso en el contexto de una llamada a un constructor de listas de inicializadores.
Ahora, la lista de inicializadores se convertirá con éxito, y la secuencia de conversión será una conversión definida por el usuario (de char const[N]
a std::string
). Cómo se hace esto se detalla en 8.5.4
nuevamente:
De lo contrario, si T es una especialización de
std::initializer_list<E>
, un objeto initializer_list se construye como se describe a continuación y se utiliza para inicializar el objeto según las reglas para la inicialización de un objeto de una clase del mismo tipo (8.5). (...)
Ver 8.5.4/4
cómo se realiza este paso final :)
Parece que funciona de esta manera:
function( {std::string("hello"), std::string("world"), std::string("test")} );
Tal vez es un error del compilador, pero tal vez está pidiendo demasiadas conversiones implícitas.