push_back emplace_back c++ vector c++11 uniform-initialization

c++ - push_back - ¿Por qué no emplace_back() usa una inicialización uniforme?



c++ emplace back (1)

El siguiente código:

#include <vector> struct S { int x, y; }; int main() { std::vector<S> v; v.emplace_back(0, 0); }

Da los siguientes errores cuando se compila con GCC:

In file included from c++/4.7.0/i686-pc-linux-gnu/bits/c++allocator.h:34:0, from c++/4.7.0/bits/allocator.h:48, from c++/4.7.0/vector:62, from test.cpp:1: c++/4.7.0/ext/new_allocator.h: In instantiation of ''void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = S; _Args = {int, int}; _Tp = S]'': c++/4.7.0/bits/alloc_traits.h:265:4: required from ''static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]'' c++/4.7.0/bits/alloc_traits.h:402:4: required from ''static void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = S; _Args = {int, int}; _Alloc = std::allocator<S>]'' c++/4.7.0/bits/vector.tcc:97:6: required from ''void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, int}; _Tp = S; _Alloc = std::allocator<S>]'' test.cpp:11:24: required from here c++/4.7.0/ext/new_allocator.h:110:4: error: new initializer expression list treated as compound expression [-fpermissive] c++/4.7.0/ext/new_allocator.h:110:4: error: no matching function for call to ''S::S(int)'' c++/4.7.0/ext/new_allocator.h:110:4: note: candidates are: test.cpp:3:8: note: S::S() test.cpp:3:8: note: candidate expects 0 arguments, 1 provided test.cpp:3:8: note: constexpr S::S(const S&) test.cpp:3:8: note: no known conversion for argument 1 from ''int'' to ''const S&'' test.cpp:3:8: note: constexpr S::S(S&&) test.cpp:3:8: note: no known conversion for argument 1 from ''int'' to ''S&&''

Sugiere que el vector está usando la sintaxis del constructor regular () para construir el elemento desde los argumentos a emplace_back() . ¿Por qué el vector no utiliza la sintaxis de inicialización uniforme {} para hacer ejemplos como el trabajo anterior?

Me parece que no hay nada que perder al usar {} (llama al constructor cuando hay uno, pero todavía funciona cuando no hay uno), y sería más en el espíritu de C ++ 11 para usar {} - después de todo, el objetivo de la inicialización uniforme es que se usa de manera uniforme, es decir, en todas partes, para inicializar objetos.


Las grandes mentes piensan igual; v). Envié un informe de defectos y sugerí un cambio al estándar sobre este tema.

http://cplusplus.github.com/LWG/lwg-active.html#2089

Además, Luc Danton me ayudó a entender la dificultad: inicialización directa frente a uniforme en std :: allocator .

Cuando se utiliza el requisito EmplaceConstructible (23.2.1 [container.requirements.general] / 13) para inicializar un objeto, se produce la inicialización directa. Inicializar un agregado o usar un constructor std :: initializer_list con emplace requiere nombrar el tipo inicializado y moverlo temporalmente. Esto es un resultado de std :: allocator :: constructo que usa sintaxis de inicialización directa, no inicialización de lista (a veces llamada "inicialización uniforme").

Alterando std :: allocator :: constructo para usar la inicialización de lista, entre otras cosas, daría preferencia a std :: initializer_list sobrecargas de constructor, rompiendo código válido de una manera intuitiva e irrevocable - no habría forma de que emplace_back accediera a un constructor adelantada por std :: initializer_list sin esencialmente reimplementar push_back.

std::vector<std::vector<int>> v; v.emplace_back(3, 4); // v[0] == {4, 4, 4}, not {3, 4} as in list-initialization

El compromiso propuesto es utilizar SFINAE con std :: is_constructible, que prueba si la inicialización directa está bien formada. Si is_constructible es falso, se elige una sobrecarga estándar std :: allocator :: construct que utiliza la inicialización de la lista. Como la inicialización de la lista siempre recae en la inicialización directa, el usuario verá los mensajes de diagnóstico como si la inicialización de la lista (inicialización uniforme) se estuviera utilizando siempre, porque la sobrecarga de inicialización directa no puede fallar.

Puedo ver dos casos de esquina que exponen lagunas en este esquema. Uno se produce cuando los argumentos destinados a std :: initializer_list satisfacen un constructor, como intentar emplace-insertar un valor de {3, 4} en el ejemplo anterior. La solución consiste en especificar explícitamente el tipo std :: initializer_list, como en v.emplace_back (std :: initializer_list (3, 4)). Dado que esto coincide con la semántica como si se dedujera std :: initializer_list, no parece haber un problema real aquí.

El otro caso es cuando los argumentos destinados a la inicialización agregada satisfacen un constructor. Como los agregados no pueden tener constructores definidos por el usuario, esto requiere que el primer miembro de datos no estático del agregado sea convertible implícitamente del tipo agregado, y que la lista de inicializadores tenga un elemento. La solución alternativa es proporcionar un inicializador para el segundo miembro. Sigue siendo imposible construir un agregado con un único miembro de datos no estático mediante la conversión de un tipo convertible al tipo propio del agregado. Esto parece un hoyo aceptablemente pequeño.