c++ - make_pair - ¿Por qué `std:: pair<int, movable>` requiere un constructor[suprimido] `const &` copy?
vector of pairs c++ (1)
Estoy ocupado probando una implementación de varios algoritmos genéricos y estoy usando tipos con soporte mínimo de las funciones proporcionadas. Encontré esta configuración extraña cuando uso un std::pair<T, movable>
con algún tipo T
(por ejemplo, int
) y un tipo movable
definido de la siguiente manera:
struct movable
{
movable() {}
movable(movable&&) = default;
// movable(movable const&) = delete;
movable(movable&) = delete;
};
La idea es tener un tipo que sea movible pero no copiable. Eso funciona muy bien, por ejemplo, con expresiones como esta:
movable m1 = movable();
movable m2 = std::move(m1);
Sin embargo, cuando se intenta usar este tipo como miembro de std::pair<...>
¡falla! Para hacer que se obtenga el código para compilar es necesario agregar el constructor de copia d (!) Que toma una movable const&
(o tener solo esa versión). El constructor de copia que toma una referencia no const
no es suficiente:
#include <utility>
auto f() -> std::pair<int, movable> {
return std::pair<int, movable>(int(), movable());
}
¿Que esta pasando aqui? Es std::pair<...>
sobreescribe especificando que std::pair(std::pair const&)
es = default
ed = default
?
El problema parece estar en la especificación del constructor de copias de std::pair
(en la sinopsis de 20.3.2 [pairs.pair]):
namespace std { template <class T1, class T2> struct pair { ... pair(const pair&) = default; ... }; }
Una comprobación rápida con mi implementación implica que la implementación obvia que copia los dos miembros no requiere la versión const&
del constructor de copia movable
. Es decir, la parte ofensiva es el = default
en el constructor de copia del pair
.
std::pair
copy constructor se declara como sigue:
pair(const pair&) = default;
Al declarar este constructor de copia para movable
:
movable(movable&) = delete;
inhibe la creación implícita de movable(const movable&)
(por lo que ni siquiera se elimina, simplemente no existe tal constructor), por lo que este es el único constructor de copia que tiene. Pero el constructor de copia std::pair
requiere un constructor de copia de sus miembros para tomar la referencia constante, por lo que se obtiene un error de compilación.
Si añades esto:
movable(movable const&) = delete;
o (mejor) simplemente quite movable(movable&) = delete;
Declaración, ahora tiene el movable(movable const&)
, y como se eliminó, el constructor de copia std::pair
también se eliminó.
Actualización : Consideremos un ejemplo más simple que demuestra el mismo problema. Esto no se compila:
template <typename T>
struct holder {
T t;
// will compile if you comment the next line
holder(holder const&) = default;
// adding or removing move constructor changes nothing WRT compile errors
// holder(holder&&) = default;
};
struct movable {
movable() {}
movable(movable&&) = default;
// will also compile if you uncomment the next line
//movable(movable const&) = delete;
movable(movable&) = delete;
};
holder<movable> h{movable()};
Se compilará si usted comenta el constructor de copia del holder
, porque así es como funciona la generación implícita del constructor de copia ( [class.copy]/8
:
El constructor de copia declarado implícitamente para una clase X tendrá la forma
X::X(const X&)
si cada subobjeto potencialmente construido de una clase de tipo
M
(o matriz de los mismos) tiene un constructor de copia cuyo primer parámetro es de tipoconst M&
oconst volatile M&
. De lo contrario, el constructor de copia declarado implícitamente tendrá la forma
X::X(X&)
Es decir, cuando comenta el holder(holder const&) = default;
la declaración holder(holder const&) = default;
El constructor de copia declarado implícitamente del holder
tendrá el holder(holder&)
del formulario holder(holder&)
. Pero si no lo hace, el constructor de copias de T
ha tomado const T&
(o const volatile T&
) porque esto es lo que se llamará en el procedimiento de copia de memberwise descrito en [class.copy]/15
.
Y si el holder
tiene un constructor de movimientos, es aún más fácil: si comenta el holder(holder const&) = default;
, el constructor de copia declarado implícitamente del holder
se eliminará.