Понимание инициализации копирования и неявных преобразований - PullRequest
0 голосов
/ 31 октября 2018

У меня возникают проблемы с пониманием, почему следующая инициализация копирования не компилируется:

#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
}

A unique_ptr<derived> можно переместить в unique_ptr<base>, так почему же второе утверждение работает, а последнее - нет? Не учитываются ли неявные конструкторы при инициализации копирования?

Ошибка из gcc-8.2.0:

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

и из clang-7.0.0 это

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

Прямой код доступен здесь .

Ответы [ 3 ]

0 голосов
/ 31 октября 2018

A std::unique_ptr<base> отличается от типа std::unique_ptr<derived>. Когда вы делаете

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

Вы вызываете конструктор преобразования std::unique_ptr<base> для преобразования pd в std::unique_ptr<base>. Это нормально, поскольку вам разрешено преобразование, определенное одним пользователем.

В

test t = std::move(pd);

Вы выполняете инициализацию копирования, поэтому вам необходимо преобразовать pd в test. Это требует 2 пользовательских преобразований, и вы не можете этого сделать. Сначала вам нужно конвертировать pd в std::unique_ptr<base>, а затем вам нужно конвертировать его в test. Это не очень интуитивно понятно, но когда у вас есть

type name = something;

независимо от того, something должно быть только одно преобразование, определенное пользователем из типа источника В вашем случае это означает, что вам нужно

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

, который использует только одного неявного пользователя, определенного как первый случай.


Давайте удалим std::unique_ptr и рассмотрим в общем случае. Поскольку std::unique_ptr<base> не совпадает с типом std::unique_ptr<derived>, у нас по существу есть

struct bar {};
struct foo
{ 
    foo(bar) {} 
};

struct test
{
    test(foo){}
};

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

и мы получаем одну и ту же ошибку , потому что нам нужно перейти от bar -> foo -> test, и для одного преобразования, определенного пользователем, слишком много.

0 голосов
/ 31 октября 2018

Семантика инициализаторов описана в [dcl.init] ¶17 . Выбор прямой инициализации и копии инициализации приводит нас к одной из двух различных позиций:

Если тип назначения является (возможно, cv-квалифицированным) типом класса:

  • [...]

  • В противном случае, если инициализация является прямой инициализацией, или если это инициализация копирования, где cv-неквалифицированная версия источника тип тот же класс, или производный класс, класса назначения, конструкторы считаются. Применимые конструкторы перечисляются ([over.match.ctor]), а лучший выбирается через разрешение перегрузки. Выбранный конструктор называется инициализировать объект с помощью выражения инициализатора или список выражений в качестве аргумента (ов). Если конструктор не применяется, или Разрешение перегрузки неоднозначно, инициализация некорректна.

  • В противном случае (т. Е. Для остальных случаев инициализации копирования) пользовательские последовательности преобразования могут преобразовываться из источника введите тип назначения или (если используется функция преобразования) к его производному классу перечисляются, как описано в [over.match.copy], а лучший выбирается через перегрузку разрешающая способность. Если преобразование не может быть сделано или является неоднозначным, инициализация плохо сформирована. Выбранная функция вызывается с выражение инициализатора в качестве аргумента; если функция конструктор, вызов является prvalue cv-неквалифицированной версии тип назначения, чей результирующий объект инициализируется конструктор. Вызов используется для прямой инициализации, в соответствии с правила выше, объект, который является пунктом назначения копирования инициализации.

В случае прямой инициализации мы вводим первый цитируемый маркер. Как там подробно описано, конструкторы рассматриваются и перечисляются напрямую. Следовательно, требуется неявная последовательность преобразования, которая должна преобразовывать unique_ptr<derived> в unique_ptr<base> в качестве аргумента конструктора.

В случае инициализации копирования мы больше не рассматриваем непосредственно конструкторы, а скорее пытаемся выяснить, какая неявная последовательность преобразования возможна. Единственный доступный - unique_ptr<derived> до unique_ptr<base> до test. Поскольку неявная последовательность преобразования может содержать только одно пользовательское преобразование, это недопустимо. Инициализация как таковая неверна.

Можно сказать, что с помощью прямой инициализации "обходится" одно неявное преобразование.

0 голосов
/ 31 октября 2018

Уверен, что только одно неявное преобразование может рассматриваться компилятором. В первом случае требуется только преобразование из std::unique_ptr<derived>&& в std::unique_ptr<base>&&, во втором случае базовый указатель также необходимо будет преобразовать в test (для работы конструктора перемещения по умолчанию). Так, например, преобразование производного указателя в базу: std::unique_ptr<base> bd = std::move(pd) и последующее перемещение, назначая его, также будет работать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...