tipos tipo puede programacion para matematicas informatica implícitamente datos convertir conversiones conversion bool c++ c++17 implicit-conversion unique-ptr

c++ - tipo - Entender la inicialización de la copia y las conversiones implícitas.



para la conversion de tipos (3)

Bastante seguro de que el compilador solo puede considerar una sola conversión implícita. En el primer caso, solo se requiere la conversión de std::unique_ptr<derived>&& a std::unique_ptr<base>&& , en el segundo caso, el puntero base también debería convertirse a test (para que funcione el constructor de movimientos predeterminado). Así, por ejemplo, convertir el puntero derivado en base: std::unique_ptr<base> bd = std::move(pd) y luego mover la asignación también funcionaría.

Tengo problemas para entender por qué no se compila la siguiente inicialización de copia:

#include <memory> struct base{}; struct derived : base{}; struct test { test(std::unique_ptr<base>){} }; int main() { auto pd = std::make_unique<derived>(); //test t(std::move(pd)); // this works; test t = std::move(pd); // this doesn''t }

Un unique_ptr<derived> se puede mover a unique_ptr<base> , entonces, ¿por qué la segunda declaración funciona pero la última no? ¿No se consideran los constructores no explícitos al realizar una inicialización de copia?

El error de gcc-8.2.0 es:

conversion from ''std::remove_reference<std::unique_ptr<derived, std::default_delete<derived> >&>::type'' {aka ''std::unique_ptr<derived, std::default_delete<derived> >''} to non-scalar type ''test'' requested

y desde Clang-7.0.0 es

candidate constructor not viable: no known conversion from ''unique_ptr<derived, default_delete<derived>>'' to ''unique_ptr<base, default_delete<base>>'' for 1st argument

El código en vivo está disponible here .


La semántica de los inicializadores se describe en [dcl.init] ¶17 . La elección de la inicialización directa frente a la inicialización de la copia nos lleva a una de dos viñetas diferentes:

Si el tipo de destino es un tipo de clase (posiblemente cv-calificado):

  • [...]

  • De lo contrario, si la inicialización es una inicialización directa, o si es una inicialización de copia donde la versión no calificada cv del tipo fuente es la misma clase que la clase de destino, o una clase derivada de la clase del destino, se consideran constructores. Los constructores correspondientes se enumeran ([over.match.ctor]), y el mejor se elige mediante resolución de sobrecarga. El constructor así seleccionado se llama para inicializar el objeto, con la expresión inicializadora o lista de expresiones como su argumento (s). Si no se aplica ningún constructor, o la resolución de sobrecarga es ambigua, la inicialización está mal formada.

  • De lo contrario (es decir, para los casos de inicialización de copia restantes), las secuencias de conversión definidas por el usuario que pueden convertirse del tipo de origen al tipo de destino o (cuando se usa una función de conversión) a una clase derivada del mismo se enumeran como se describe en [sobre .match.copy], y el mejor se elige mediante resolución de sobrecarga. Si la conversión no se puede realizar o es ambigua, la inicialización no se ha realizado correctamente. La función seleccionada se llama con la expresión inicializadora como su argumento; Si la función es un constructor, la llamada es un prvalor de la versión no calificada de CV del tipo de destino cuyo objeto de resultado es inicializado por el constructor. La llamada se utiliza para inicializar directamente, de acuerdo con las reglas anteriores, el objeto que es el destino de la inicialización de la copia.

En el caso de inicialización directa, ingresamos la primera viñeta entre comillas. Como se detalla allí, los constructores son considerados y enumerados directamente. La secuencia de conversión implícita que se requiere es, por lo tanto, solo para convertir unique_ptr<derived> en un unique_ptr<base> como un argumento de constructor.

En el caso de inicialización de copia, ya no estamos considerando directamente a los constructores, sino que intentamos ver qué secuencia de conversión implícita es posible. El único disponible es unique_ptr<derived> para un unique_ptr<base> para una test . Dado que una secuencia de conversión implícita puede contener solo una conversión definida por el usuario, esto no está permitido. Como tal, la inicialización está mal formada.

Se podría decir que el uso de la inicialización directa de "omite" una conversión implícita.


Un std::unique_ptr<base> no es el mismo tipo que un std::unique_ptr<derived> . Cuando tu lo hagas

test t(std::move(pd));

Llama al constructor de conversión de std::unique_ptr<base> para convertir pd en un std::unique_ptr<base> . Esto está bien ya que se le permite una única conversión definida por el usuario.

En

test t = std::move(pd);

Está haciendo la inicialización de copias, por lo que necesita convertir pd en una test . Eso requiere 2 conversiones definidas por el usuario y no puedes hacer eso. Primero debe convertir pd a un std::unique_ptr<base> y luego debe convertirlo en una test . No es muy intuitivo pero cuando tienes

type name = something;

Cualquier something sea ​​debe ser solo una conversión definida por el usuario desde el tipo de fuente. En tu caso eso significa que necesitas

test t = test{std::move(pd)};

que solo usa un único usuario implícito definido como lo hace el primer caso.

Permite eliminar std::unique_ptr y verlo en un caso general. Dado que std::unique_ptr<base> no es del mismo tipo que std::unique_ptr<derived> esencialmente tenemos

struct bar {}; struct foo { foo(bar) {} }; struct test { test(foo){} }; int main() { test t = bar{}; }

y obtenemos el mismo error porque debemos ir desde la bar -> foo -> test y eso tiene una conversión definida por el usuario demasiadas.