Pregunta Fallo de conversión implícita de la lista de inicializadores


Considera el fragmento:

#include <unordered_map>

void foo(const std::unordered_map<int,int> &) {}

int main()
{
        foo({});
}

Esto falla con GCC 4.9.2 con el mensaje:

map2.cpp:7:19: error: converting to ‘const std::unordered_map<int, int>’ from initializer list would use explicit constructor ‘std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map(std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type, const hasher&, const key_equal&, const allocator_type&) [with _Key = int; _Tp = int; _Hash = std::hash<int>; _Pred = std::equal_to<int>; _Alloc = std::allocator<std::pair<const int, int> >; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type = long unsigned int; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::hasher = std::hash<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::key_equal = std::equal_to<int>; std::unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::allocator_type = std::allocator<std::pair<const int, int> >]’

Prueba con otras implementaciones de compilador / biblioteca:

  • GCC <4.9 acepta esto sin quejarse,
  • Clang 3.5 con libstdc ++ falla con un mensaje similar,
  • Clang 3.5 con libc ++ acepta esto,
  • ICC 15.algo lo acepta (no estoy seguro de qué biblioteca estándar está usando).

Un par de puntos más desconcertantes:

  • reemplazando std::unordered_map con std::map hace desaparecer el error,
  • reemplazando foo({}) con foo foo({{}}) también hace que el error desaparezca.

Además, reemplazando {} con una lista de inicializadores no vacía funciona como se espera en todos los casos.

Entonces mis preguntas principales son:

  • ¿Quién está aquí? ¿El código de arriba está bien formado?
  • ¿Qué significa la sintaxis con llaves dobles? foo({{}}) exactamente hacer para que desaparezca el error?

EDITAR corrigió un par de errores tipográficos


36
2017-11-15 15:59


origen


Respuestas:


La sintaxis de inicialización indirecta con un braced-init-list su código está usando se llama copia-lista-inicialización.

El procedimiento de resolución de sobrecarga que selecciona el mejor constructor viable para ese caso se describe en la siguiente sección del Estándar C ++:

§ 13.3.1.7 Inicialización por inicialización de listas [over.match.list]

  1. Cuando los objetos del tipo de clase no agregada T se inicializan en lista (8.5.4), la resolución de sobrecarga selecciona el constructor   en dos fases:

    - Inicialmente, las funciones candidatas son los constructores de listas de inicializadores (8.5.4) de la clase T y la lista de argumentos consiste en la lista de inicializadores como un único argumento.

    - Si no se encuentra un constructor viable de lista de inicializadores, la resolución de sobrecarga se realiza nuevamente, donde las funciones candidatas son todos los constructores de la clase. T y la lista de argumentos consta de los elementos de la lista de inicializadores.

Si la lista de inicializadores no tiene elementos y T tiene un constructor por defecto, la primera fase se omite. En copy-list-initialization, si se elige un constructor explícito, la inicialización está mal formada. [ Nota: Esto difiere de otras situaciones (13.3.1.3, 13.3.1.4), donde solo se consideran los constructores de conversión para la inicialización de la copia. Esta restricción solo se aplica si esta inicialización es parte del resultado final de la resolución de sobrecarga. - nota final ]

De acuerdo con eso, una initializer-list-constructor (el que se puede llamar con un único argumento que coincide con el parámetro de tipo del constructor std::initializer_list<T>) es generalmente preferido a otros constructores, pero no si hay un constructor por defecto disponible, y el braced-init-list usado para lista de inicialización  esta vacio.

Lo que es importante aquí, el conjunto de constructores de los contenedores de la biblioteca estándar ha cambiado entre C ++ 11 y C ++ 14 debido a Número LWG 2193. En caso de std::unordered_mapPor el bien de nuestro análisis, nos interesa la siguiente diferencia:

C ++ 11:

explicit unordered_map(size_type n = /* impl-defined */,
                     const hasher& hf = hasher(),
                     const key_equal& eql = key_equal(),
                     const allocator_type& alloc = allocator_type());

unordered_map(initializer_list<value_type> il,
            size_type n = /* impl-defined */,
            const hasher& hf = hasher(),
            const key_equal& eql = key_equal(),
            const allocator_type& alloc = allocator_type());

C ++ 14:

unordered_map();

explicit unordered_map(size_type n,
                     const hasher& hf = hasher(),
                     const key_equal& eql = key_equal(),
                     const allocator_type& alloc = allocator_type());

unordered_map(initializer_list<value_type> il,
            size_type n = /* impl-defined */,
            const hasher& hf = hasher(),
            const key_equal& eql = key_equal(),
            const allocator_type& alloc = allocator_type());

En otras palabras, hay un diferente Constructor predeterminado (el que se puede llamar sin argumentos) según el estándar de lenguaje (C ++ 11 / C ++ 14), y, lo que es crucial, el constructor predeterminado en C ++ 14 ahora se hace no-explicit.

Ese cambio fue introducido para que uno pueda decir:

std::unordered_map<int,int> m = {};

o:

std::unordered_map<int,int> foo()
{
    return {};
}

que son semánticamente equivalentes a su código ( {} como el argumento de una llamada de función para inicializar std::unordered_map<int,int>)

Es decir, en el caso de una biblioteca conforme a C ++ 11, el error es esperado, ya que el constructor seleccionado (predeterminado) es explicit, por lo tanto, el código es mal formado:

explicit unordered_map(size_type n = /* impl-defined */,
                     const hasher& hf = hasher(),
                     const key_equal& eql = key_equal(),
                     const allocator_type& alloc = allocator_type());

En el caso de una biblioteca compatible con C ++ 14, el error es no esperado, ya que el constructor seleccionado (predeterminado) es no  explicity el código es bien formado:

unordered_map();

Como tal, el comportamiento diferente que encuentras está relacionado únicamente con la versión de libstdc ++ y libc ++ que estás utilizando con diferentes compiladores / opciones de compilación.


Reemplazando std::unordered_map con std::map hace que el error desaparezca ¿Por qué?

Sospecho que es solo porque std::map en la versión de libstdc ++ que está utilizando ya estaba actualizado para C ++ 14.


Reemplazando foo({}) con foo({{}}) también hace que el error desaparezca. ¿Por qué?

Porque ahora esto es copia-lista-inicialización  {{}} con un no vacio  braced-init-list (es decir, tiene un elemento adentro, inicializado con un vacío braced-init-list  {}), entonces la regla de la primera fase de § 13.3.1.7 [over.match.list] / p1 (citada anteriormente) que prefiere una initializer-list-constructor a otros se aplica. Ese constructor no es explicit, por lo tanto, la llamada es bien formado.


Reemplazando {} con una lista de inicializadores no vacía funciona como se espera en todos los casos. ¿Por qué?

Igual que arriba, la resolución de sobrecarga termina con la primera fase de § 13.3.1.7 [over.match.list] / p1.


33
2017-11-15 18:16



La inicialización de lista para referencias se define de la siguiente manera, [dcl.init.list] / 3:

De lo contrario, si T es un tipo de referencia, un prvalue temporal del tipo   referenciado por T está inicializado en lista de copia o inicializado en lista directa,   dependiendo del tipo de inicialización para la referencia, y el   la referencia está ligada a eso temporal.

Entonces tu código falla porque

std::unordered_map<int,int> m = {};

falla La inicialización de la lista para este caso está cubierta a través de esta viñeta de [dcl.init.list] / 3:

De lo contrario, si la lista de inicializadores no tiene elementos y T es una clase   escriba con un constructor predeterminado, el objeto se inicializa en valor.

Entonces se llamará al constructor por defecto del objeto1.
Ahora a los bits cruciales: En C ++ 11, unordered_map tenía este constructor predeterminado2:

explicit unordered_map(size_type n = /* some value */ ,
                       const hasher& hf = hasher(),
                       const key_equal& eql = key_equal(),
                       const allocator_type& a = allocator_type());

Claramente, llamar esto explicit constructor mediante copy-list-initialization está mal formado, [over.match.list]:

En la inicialización de la lista de copia, si explicit se elige el constructor, la inicialización está mal formada.

Desde C ++ 14 unordered_map declara un constructor predeterminado que no es explícito:

unordered_map();

Por lo tanto, una implementación estándar de la biblioteca C ++ 14 debe compilar esto sin problemas. Presumiblemente, libc ++ ya está actualizado, pero libstdc ++ se está quedando atrás.


1) [dcl.init] / 7:

A value-initialize un objeto de tipo T medio:
 - Si T es un   (posiblemente cv-calificado) tipo de clase (Cláusula 9) con un usuario proporcionado   constructor (12.1), luego el constructor predeterminado para T se llama   [...];

2) [class.ctor] / 4:

Un constructor predeterminado para una clase X es un constructor de clase X eso se puede llamar sin un argumento.


4
2017-11-15 18:42