c++ - texto - ejemplos de argumentos
¿Por qué necesito usar piecewise_construct en map:: emplace para constructores de argumentos únicos de objetos no copiables? (1)
Por lo que puedo decir, el problema no se debe a map::emplace
, sino a los constructores de pair
:
#include <map>
struct A
{
A(int) {}
A(A&&) = delete;
A(A const&) = delete;
};
int main()
{
std::pair<int, A> x(1, 4); // error
}
Este ejemplo de código no se compila, ni con g ++ 4.8.1 de coliru ni con clang ++ 3.5, que están usando libstdc ++, por lo que puedo decir.
El problema está arraigado en el hecho de que aunque podemos construir
A t(4);
es decir, std::is_constructible<A, int>::value == true
, no podemos convertir implícitamente un int
en un A
[conv] / 3
Una expresión
e
puede convertirse implícitamente a un tipoT
si y solo si la declaraciónT t=e;
Está bien formado, por alguna variable temporal inventadat
.
Tenga en cuenta la copia de inicialización (el =
). Esto crea una A
temporal e inicializa t
desde este temporal, [dcl.init] / 17. Esta inicialización desde un intento temporal llama al ctor de movimiento eliminado de A
, lo que hace que la conversión no esté bien formada.
Como no podemos convertir de un int
a un A
, el constructor de pair
que uno esperaría que se llame será rechazado por SFINAE . Este comportamiento es sorprendente, N4387: mejora los análisis de pares y tuplas e intenta mejorar la situación, haciendo que el constructor sea explicit
lugar de rechazarlo. N4387 ha sido votado en C ++ 1z en la reunión de Lenexa.
A continuación se describen las reglas de C ++ 11.
El constructor que esperaba que me llamaran se describe en [pairs.pair] / 7-9
template<class U, class V> constexpr pair(U&& x, V&& y);
7 Requiere:
is_constructible<first_type, U&&>::value
istrue
yis_constructible<second_type, V&&>::value
estrue
.8 Efectos: el constructor inicializa primero con
std::forward<U>(x)
y segundo constd::forward<V>(y)
.9 Observaciones: Si
U
no es convertible implícitamente afirst_type
oV
no es implícitamente convertible asecond_type
este constructor no participará en la resolución de sobrecarga.
Note la diferencia entre is_constructible
en la sección de is_constructible
y "no es convertible implícitamente" en la sección de comentarios . Se cumplen los requisitos para llamar a este constructor, pero no puede participar en la resolución de sobrecarga (= tiene que ser rechazado a través de SFINAE).
Por lo tanto, la resolución de sobrecarga debe seleccionar una "coincidencia peor", es decir, una cuyo segundo parámetro es una A const&
. Se crea un temporal a partir del argumento int
y se vincula a esta referencia, y la referencia se utiliza para inicializar el miembro de datos de pair
( .second
). La inicialización intenta llamar a la copia eliminada ctor de A
, y la construcción del par está mal formada.
libstdc ++ tiene (como extensión) algunos controladores no estándar. En el último doxygen (y en 4.8.2), el constructor del pair
que esperaba llamar (sorprendiéndome por las reglas requeridas por el Estándar) es:
template<class _U1, class _U2,
class = typename enable_if<__and_<is_convertible<_U1, _T1>,
is_convertible<_U2, _T2>
>::value
>::type>
constexpr pair(_U1&& __x, _U2&& __y)
: first(std::forward<_U1>(__x)), second(std::forward<_U2>(__y)) { }
y el que realmente se llama es el no estándar:
// DR 811.
template<class _U1,
class = typename enable_if<is_convertible<_U1, _T1>::value>::type>
constexpr pair(_U1&& __x, const _T2& __y)
: first(std::forward<_U1>(__x)), second(__y) { }
El programa está mal formado de acuerdo con el Estándar, no es simplemente rechazado por este ctor no estándar.
Como observación final, aquí está la especificación de is_constructible
y is_convertible
.
is_constructible
[meta.rel] / 4
Dada la siguiente función del prototipo:
template <class T> typename add_rvalue_reference<T>::type create();
la condición de predicado para una especialización de plantilla
is_constructible<T, Args...>
cumplirá si y solo si la siguiente definición de variable estaría bien formada para alguna variable inventadat
:
T t(create<Args>()...);
[ Nota: estos tokens nunca se interpretan como una declaración de función. - nota final ] La verificación de acceso se realiza como en un contexto no relacionado con
T
y cualquiera de losArgs
. Solo se considera la validez del contexto inmediato de la inicialización de la variable.
is_convertible
[meta.unary.prop] / 6:
Dada la siguiente función del prototipo:
template <class T> typename add_rvalue_reference<T>::type create();
la condición de predicado para una especialización de plantilla
is_convertible<From, To>
cumplirá si y solo si la expresión de retorno en el siguiente código estaría bien formada, incluidas las conversiones implícitas al tipo de retorno de la función:
To test() { return create<From>(); }
[ Nota: este requisito proporciona resultados bien definidos para tipos de referencia, tipos de nulos, tipos de matrices y tipos de funciones. - nota final ] La verificación de acceso se realiza como en un contexto no relacionado con
To
yFrom
. Solo se considera la validez del contexto inmediato de la expresión de la declaración de retorno (incluidas las conversiones al tipo de retorno).
Para tu tipo A
,
A t(create<int>());
está bien formado; sin embargo
A test() {
return create<int>();
}
crea un temporal de tipo A
e intenta moverlo al valor de retorno (inicialización de la copia). Eso selecciona el ctor A(A&&)
eliminado y, por lo tanto, está mal formado.
El siguiente código no se compilará en gcc 4.8.2. El problema es que este código intentará copiar la construcción de un std::pair<int, A>
que no puede suceder debido a la struct A
falta de copia y mover constructores.
¿Está fallando gcc aquí o me estoy perdiendo algo?
#include <map>
struct A
{
int bla;
A(int blub):bla(blub){}
A(A&&) = delete;
A(const A&) = delete;
A& operator=(A&&) = delete;
A& operator=(const A&) = delete;
};
int main()
{
std::map<int, A> map;
map.emplace(1, 2); // doesn''t work
map.emplace(std::piecewise_construct,
std::forward_as_tuple(1),
std::forward_as_tuple(2)
); // works like a charm
return 0;
}