compilador - gcc c++
Fallo de conversión implícita de la lista de inicializadores (2)
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 al que hace referenciaT
es copy-list-initialized o direct-list-initialized, dependiendo del tipo de inicialización para la referencia, y la referencia está vinculada a ese 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 un tipo de clase con un constructor predeterminado, el objeto se inicializa en valores.
Por lo tanto, el constructor predeterminado del objeto se llamará 1 .
Ahora a los bits cruciales: en C ++ 11, unordered_map
tenía este constructor predeterminado 2 :
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 a este constructor explicit
través de copy-list-initialization está mal formado, [over.match.list]:
En copy-list-initialization, si se elige un constructor
explicit
, la inicialización está mal formada.
Como 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:Valorizar-inicializar un objeto de tipo
T
significa:
- siT
es un tipo de clase (posiblemente cv calificado) (Cláusula 9) con un constructor proporcionado por el usuario (12.1), entonces se llama al constructor predeterminado paraT
[...];
2) [class.ctor] / 4:
Un constructor predeterminado para una clase
X
es un constructor de claseX
que se puede llamar sin un argumento.
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
constd::map
hace que el error desaparezca, - reemplazar
foo({})
con foofoo({{}})
también hace que el error desaparezca.
Además, reemplazar {}
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é hace exactamente la sintaxis con llaves dobles
foo({{}})
para hacer desaparecer el error?
EDIT corrigió un par de errores tipográficos.
La sintaxis de inicialización indirecta con una lista inicial arrinconada que usa su código 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 lista
[over.match.list]
Cuando los objetos del tipo de clase no agregada
T
se inicializan en la lista (8.5.4), la resolución de sobrecarga selecciona el constructor en dos fases:- Inicialmente, las funciones candidatas son los constructores de lista de inicializadores (8.5.4) de la clase
T
y la lista de argumentos consta de la lista de inicializadores como un único argumento.- Si no se encuentra un constructor viable de listas 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 predeterminado, 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 esto, un inicializador-lista-constructor (el que se puede llamar con un único argumento que coincide con el parámetro del constructor de tipo std::initializer_list<T>
) generalmente es preferible a otros constructores, pero no si hay un constructor predeterminado disponible , y la lista de inicialización utilizada para la inicialización de la lista está vacía .
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 al problema LWG 2193 . En el caso de std::unordered_map
, por 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 constructor predeterminado diferente (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 (pasando {}
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, se espera el error, ya que el constructor seleccionado (predeterminado) es explicit
, por lo tanto, el código está 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 conforme a C ++ 14, el error no es esperado , ya que el constructor seleccionado (predeterminado) no es explicit
, y el código está 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.
Al reemplazar
std::unordered_map
porstd::map
el error desaparece. ¿Por qué?
Sospecho que es solo porque std::map
en la versión de libstdc ++ que está utilizando ya estaba actualizado para C ++ 14.
Reemplazar
foo({})
confoo({{}})
también hace que el error desaparezca. ¿Por qué?
Porque ahora esto es copy-list-initialization {{}}
con una lista de inicios arriostrados no vacía (es decir, tiene un elemento adentro, inicializado con una validación de lista de llaves vacía {}
), por lo que la regla de se aplica la primera fase de § 13.3.1.7 [over.match.list] / p1 (citada anteriormente) que prefiere un inicializador-lista-constructor a otros. Ese constructor no es explicit
, por lo tanto, la llamada está bien formada .
Reemplazar
{}
con una lista de inicializador 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.