pairs make_pair example ejemplo c++ c++11 stl c++-standard-library

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 tipo const M& o const 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á.