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á.