是派生类类型到由用户定义的转换包含的基类类型



由于阅读了cplusplus标准中的一些引号,我对从派生类类型转换基类类型感到困惑。此转换是否属于用户定义的转换?

引用一些让我对此感到困惑的话:

[class.conv]/1类对象的类型转换可以由构造函数和转换函数指定。这些转换称为用户定义的转换,用于隐式类型转换。。。

[class.conv.ctor]/3
非显式复制/移动构造函数([class.copy])是一个转换构造函数。

#include <iostream>
struct Base{
Base() = default;
Base(Base const&){}
};
struct Derived:Base{
};
int main(){
Derived d;
Base b = d; //from d to b, is this a user-defined conversion? Before reading the standard, I think it's not, but now I'm confused about this.
}

因此,根据这些引号,派生类类型对象到基类类型属于用户定义的转换。如果我错过了标准中写的关于派生类类型到基类类型不属于用户定义转换的内容,请纠正我。

好吧,让我们来分解一下标准是怎么说的:

类对象的类型转换可以由构造函数和转换函数指定。

现在,让我们假装对这些词的含义一无所知。这句话讲的是一个叫做"类型转换"的概念,但它具体讲的是"类对象的类型转换"。所以我们并不是在谈论所有的类型转换,只是其中的一个子集。

然后它说"可以指定",并列出了可以指定它们的几种方式。下一句:

这些转换称为用户定义转换

注意,它没有说"这些构造函数"或"这些转换函数"。它说的是"这些转换"。好吧,唯一讨论过的"转换"是前面讨论过的子集:"类对象的类型转换"。因此,这句话可以重述为:

[类对象的类型转换]称为用户定义的转换。

因此,我们可以从中看出,类对象可以进行类型转换。这些转换可以由类上的某些东西指定。这种特殊的类型转换被称为"用户定义的转换"。

在任何时候,标准都没有说构造函数本身要么是类型转换,要么是用户定义的转换。构造函数只是指定这种转换的一种方法。

接下来,我们转到[class.conv.ctor]/1:

在没有函数说明符显式声明的构造函数指定从其参数类型(如果有)到其类类型的转换。这样的构造函数称为转换构造函数。

好了,我们现在有了"转换构造函数"的定义。事实上,给定这个定义,第3段(声明非explicit复制/移动构造函数正在转换构造函数)是多余的;上面的定义清楚地表明了它们是。

作为"转换构造函数"是构造函数的一个属性。用户定义转换的过程是详细说明的,它当然可以调用"转换构造函数"。但在任何时候都没有声明或暗示这是一个只能调用"转换构造函数"的过程。

因此,复制构造函数是"转换构造函数"这一事实不应被解释为导致调用复制构造函数的任何东西本身就是用户定义的转换。当标准发生时,就会发生用户定义的转换。

在您描述的示例中,发生的情况在[dcl.init]/17.6.2:中进行了定义

否则,如果初始化是直接初始化,或者如果是复制初始化,其中源类型的cv不合格版本与目标类的类或派生类相同,则考虑构造函数。枚举适用的构造函数([over.match.ctor]),并通过重载解析选择最佳构造函数。调用如此选择的构造函数来初始化对象,并将初始值设定项表达式或表达式列表作为其参数。如果没有应用构造函数,或者重载解析不明确,则初始化格式不正确。

在这个规则本身的任何地方都没有声明直接执行任何类型的转换。结果是对目标类型的单参数构造函数进行重载解析。重载解析规则可以考虑许多转换,因为它试图将给定的参数与重载集中的各种参数可能性相匹配。但这些都是通用的,与任何函数调用的任何重载解决方案相关。

也就是说,所选函数恰好被视为"转换构造函数",这并不意味着用户定义的转换导致了它被调用。

此处不考虑用户定义的转换。该标准列出了复制初始化的两种情况(语法Base b = d;是其中的一种形式)。它们是

[dcl.init]/17.16.2

否则,如果初始化是直接初始化,或者如果是复制初始化,其中源类型的cv不合格版本与目标类的类或派生类相同,则考虑构造函数。。。

[dcl.init]/17.16.3

否则(即,对于剩余的副本初始化情况),可以从源类型转换为目标类型的用户定义转换。。。[已使用]。

由于Derived是从Base派生而来的,因此这种情况使用前者子句,而不是后者。因此,仅咨询Base的构造函数,而不咨询例如可以在Derived中定义的任何operator Base()

最新更新