无法使用带有多个隐式转换步骤的复制初始化[重复]

问题描述 投票:0回答:3

我无法理解为什么以下复制初始化无法编译:

#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

可在此处获取实时代码。

c++ initialization c++17 implicit-conversion unique-ptr
3个回答
20
投票

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
开始,并且一个用户定义的转换太多。


5
投票

初始化器的语义在 [dcl.init] ¶17 中描述。直接初始化与复制初始化的选择将我们带入两种不同的项目之一:

如果目标类型是(可能是 cv 限定的)类类型:

  • [...]

  • 否则,如果初始化是直接初始化,或者是复制初始化,其中源的 cv 不合格版本 type 是与该类相同的类,或者是该类的派生类 目的地,构造函数被考虑。适用的构造函数 被枚举([over.match.ctor]),并且通过选择最好的一个 过载决议。如此选择的构造函数被调用 使用初始化表达式或 表达式列表作为其参数。如果没有构造函数适用,或者 重载解析不明确,初始化格式错误。

  • 否则(即,对于其余的复制初始化情况),可以从源进行转换的用户定义的转换序列 类型转换为目标类型或(当使用转换函数时) 其派生类的枚举如下所述 [over.match.copy],通过重载选择最好的一个 解决。如果转换无法完成或不明确,则 初始化格式不正确。所选择的函数被调用 初始化表达式作为其参数;如果函数是 构造函数,调用是 cv 未限定版本的纯右值 其结果对象由以下项初始化的目标类型 构造函数。该调用用于直接初始化,根据 上面的规则中,作为目的地的对象 复制初始化。

在直接初始化的情况下,我们输入第一个引用的项目符号。正如那里所详述的,构造函数被直接考虑和枚举。因此,所需的隐式转换序列只是将

unique_ptr<derived>
转换为
unique_ptr<base>
作为构造函数参数。

在复制初始化的情况下,我们不再直接考虑构造函数,而是尝试看看哪种隐式转换序列是可能的。唯一可用的是

unique_ptr<derived>
unique_ptr<base>
test
。由于隐式转换序列只能包含一个用户定义的转换,因此这是不允许的。因此初始化是不正确的。

可以说,使用直接初始化“绕过”了一种隐式转换。


4
投票

非常确定编译器只允许考虑单个隐式转换。在第一种情况下,仅需要从

std::unique_ptr<derived>&&
std::unique_ptr<base>&&
的转换,在第二种情况下,基指针也需要转换为
test
(默认移动构造函数才能工作)。 因此,例如将派生指针转换为基数:
std::unique_ptr<base> bd = std::move(pd)
,然后移动分配它也可以工作。

© www.soinside.com 2019 - 2024. All rights reserved.