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
epuede convertirse implícitamente a un tipoTsi 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&&>::valueistrueyis_constructible<second_type, V&&>::valueestrue.8 Efectos: el constructor inicializa primero con
std::forward<U>(x)y segundo constd::forward<V>(y).9 Observaciones: Si
Uno es convertible implícitamente afirst_typeoVno es implícitamente convertible asecond_typeeste 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
Ty 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
ToyFrom. 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;
}