【问题标题】:Is derived class type to base class type subsumed by user-defined conversion?派生类类型到基类类型是否包含在用户定义的转换中?
【发布时间】:2020-07-19 03:51:35
【问题描述】:

自从阅读了 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.
}

所以根据这些引用,派生类类型对象到基类类型属于用户定义的转换。如果我遗漏了标准中写的派生类类型到基类类型不属于用户定义转换的内容,请纠正我。

【问题讨论】:

  • 我没有看到您引用的任何内容说派生到基础的转换是用户定义的。被视为转换构造函数的复制构造函数与派生到基转换的性质无关。毕竟,复制构造函数不进行派生到基的转换。那么为什么你认为这两个不相关的引用是相关的呢?
  • @NicolBolas 在代码中`Base b = d;`调用了Base::Base(Base const&),它是一个拷贝构造函数,根据这些引用,拷贝构造函数是一个转换构造函数,所以我认为是
  • @NicolBolas ??? 复制构造函数 do 执行派生到基的转换。上面的代码调用复制构造函数Base(Base const&amp;)d初始化b
  • @jackX:派生到基的转换发生在初始化传递给复制构造函数的参数期间。调用复制构造函数的行为本身并不是一种转换。
  • @NicolBolas "没有这样的标准转换;这种从派生到基的转换只存在于隐式转换序列的描述中" [[over.best.ics]/6](timsong-cpp.github.io/cppwp/n4659/… ),[[over.ics.ref]/1](timsong-cpp.github.io/cppwp/n4659/…),我认为从参数d到参数Base const&amp;,如你所说,是一个derived-to-base conversions,但提到Base::Base(Base const&amp;)的创新在标准中,它本身就是一个复制构造

标签: c++ language-lawyer


【解决方案1】:

好的,让我们分解一下标准的含义:

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

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

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

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

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

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

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

标准从来没有说构造函数本身是类型转换或用户定义的转换。构造函数只是指定此类转换的一种方式。

接下来,我们继续[class.conv.ctor]/1

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

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

作为“转换构造函数”是构造函数的属性。 user-defined conversion 的过程被详细说明,它当然可以调用“转换构造函数”。但在任何时候都没有声明或暗示这是唯一可以调用“转换构造函数”的过程。

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

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

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

在这条规则本身中没有任何地方声明直接执行任何类型的转换。发生的是目标类型的单参数构造函数的重载决议。重载决议的规则可以考虑许多转换,因为它试图使给定的参数适合重载集中的各种参数可能性。但这些都是通用的,与任何函数调用的任何重载决议相关。

也就是说,刚刚选择的函数恰好被视为“转换构造函数”这一事实并不意味着用户定义的转换导致它被调用。

【讨论】:

  • @NicoBolas 一个非常详细的答案。非常棒。我明白了,你的意思是一个类的构造函数都被称为“转换构造函数”并且只有当它被用户定义使用转换顺序,那就叫用户自定义转换,否则它只是一个简单的“转换构造函数”。所有用户自定义转换标准都在引用中明确定义了什么时候需要?你同意我的观点吗?
  • @jackX:你把事情复杂化了。转换构造函数只是 constructor 的一个范畴。它们唯一的特别之处是用户定义的转换操作只会查看那些构造函数。而已;在其他方式中,它们只是构造函数。
  • @NicoBolas 是的,我同意你的看法。简单来说,它是一个构造函数,但在 [[over.ics.user]](timsong-cpp.github.io/cppwp/n4659/over.ics.user#1) 它还说“如果用户定义转换由构造函数指定”,所以我认为如果构造函数被用户定义的转换序列使用,那么该构造函数的特殊类别称为用户定义转换,否则它只是你说的普通转换构造函数。是吗?
【解决方案2】:

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

[dcl.init]/17.16.2

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

[dcl.init]/17.16.3

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

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

【讨论】:

  • @NicolBolas 很好,我已经扔掉了那段。
  • 如何解释“将类类型的表达式转换为相同的类类型被赋予精确匹配等级,并且将类类型的表达式转换为该类型的基类被赋予转换等级,尽管在这些情况下调用了构造函数(即用户定义的转换函数)。”在 [[over.ics.user]](timsong-cpp.github.io/cppwp/n4659/over.ics.user#4)
  • 问题本身是从db,调用Base 的复制构造函数,并且标准说“非显式复制/移动构造函数([class.copy ]) 是一个转换构造函数”和“类对象的类型转换可以由构造函数和转换函数指定。这些转换称为用户定义的转换” 注意 用户定义的转换
  • 如果您有struct Base { }; struct Derived : Base { }; struct Arg { Arg(Base) { } }; void f(Arg); Derived d;,那么f(d) 需要从d 构造一个Base,然后从Base 构造一个Arg;也就是说,两个构造函数调用。通常,您只允许调用一次用户定义的转换函数,但我相信您引用的条款说从DerivedBase 的转换被视为转换等级标准转换,而不是占用您拥有的唯一一个用户定义的转换“槽”。不过,该条款不适用于这个问题。
  • 但标准实际上是说“类对象的类型转换可以由构造函数和转换函数指定。这些转换称为用户定义的转换”这意味着构造函数的调用是用户定义的转换,如何解释
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多