【问题标题】:Explicit conversion functions, direct-initialization, and converting constructors显式转换函数、直接初始化和转换构造函数
【发布时间】:2012-09-11 14:49:07
【问题描述】:

后标准草案 n3376 以使用显式转换函数到用户定义类型的示例 (12.3.2:2) 为例:

class Y { };
struct Z {
  explicit operator Y() const;
};
void h(Z z) {
  Y y1(z); // OK: direct-initialization
}

根据 12.3.2:2,显式转换函数“仅被视为用户定义的直接初始化转换”;但是,这似乎允许:

struct Y { Y(int); };
struct Z {
  explicit operator int() const;
};
void h(Z z) {
  Y y1(z); // direct-initialization
}

这似乎与标准的意图相冲突,并且确实被 gcc-4.7.1 拒绝:

source.cpp: In function 'void h(Z)':
source.cpp:4:9: error: no matching function for call to 'Y::Y(Z&)'
source.cpp:4:9: note: candidates are:
source.cpp:1:12: note: Y::Y(int)
source.cpp:1:12: note:   no known conversion for argument 1 from 'Z' to 'int'
source.cpp:1:8: note: constexpr Y::Y(const Y&)
source.cpp:1:8: note:   no known conversion for argument 1 from 'Z' to 'const Y&'
source.cpp:1:8: note: constexpr Y::Y(Y&&)
source.cpp:1:8: note:   no known conversion for argument 1 from 'Z' to 'Y&&'

gcc 通过int 拒绝从ZY 的转换是否正确,或者标准确实允许这种用法?

我考虑了上述直接初始化的上下文;根据 8.5:16 中对类类型的直接初始化定义,使用初始化表达式作为其参数调用构造函数,因此通过隐式转换序列 (13.3.3.1) 将其转换为参数类型。由于隐式转换序列是隐式转换 (4:3),因此模型复制初始化 (8.5:14) 而不是直接初始化,因此 12.3.2:2 中的语言必须将表达式作为一个整体来引用。

另请注意,这并不违反 12.3:4(多个用户定义的转换);相同的编译器对删除 explicit 的相同代码感到满意(Clang 和 Comeau 也是如此):

struct Y { Y(int); };
struct Z { operator int(); };
void h(Z z) {
  Y y1(z); // direct-initialization
}

我认为 Jesse Good 已经确定了 13.3.1.4:1 中 operator Yoperator int 案例之间的区别,但我仍然担心第三种情况:

struct X {};
struct Y { Y(const X &); };
struct Z {
  explicit operator X() const;
};
void h(Z z) {
  Y y1(z); // direct-initialization via class-type X
}

临时X 的初始化将绑定到Y 的构造函数的单个const X & 参数,按照13.3.1.4:1 在直接初始化上下文中进行,TXSZ。我认为这个条款是错误的,应该是:

13.3.1.4 通过用户定义的转换复制初始化类[over.match.copy]

1 - [...] 初始化临时绑定到第一个参数时 一个构造函数,该构造函数将引用可能 cv 限定的 T 作为其第一个参数,在“cv2 @”类型的对象的直接初始化 上下文中使用单个参数调用987654340@",还考虑了显式转换函数。 [...]

为了避免混淆,我认为12.3.2:2也应该修改一下:

12.3.2 转换函数[class.conv.fct]

2 - 转换函数可能是显式的 (7.1.2),在这种情况下,它仅被视为直接初始化 (8.5) 的用户定义转换在某些情况下 (13.3.1.4, 13.3. 1.5、13.3.1.6)。 [...]

上面有cmet吗?

【问题讨论】:

  • 如果您认为措辞可以改进,我实际上会推荐 C++ 讨论组之一。
  • @LucDanton 我想说我没有时间参加讨论组,但显然不是这样。

标签: c++ c++11 initialization type-conversion language-lawyer


【解决方案1】:

根据 8.5 和 13.3.1.3,考虑了 Y 的构造函数,并通过重载决议挑选出最好的构造函数。在这种情况下,相关的构造函数是 Y(int); 以及复制和移动构造函数。在重载解决过程中 13.3.2 Viable functions [over.match.viable] 指定:

3 其次,为了使F 成为一个可行的函数,每个参数都应存在一个隐式转换序列 (13.3.3.1),该序列将该参数转换为F 的相应参数. [...]

对于所有这些构造函数,都没有从ZintY 的一种转换。为了说服自己,让我们研究一下标准在 13.3.3.1 隐式转换序列 [over.best.ics] 中对隐式转换序列的说法:

1 隐式转换序列是用于将函数调用中的参数转换为被调用函数的相应参数类型的转换序列。转换序列是第 4 条中定义的隐式转换,这意味着它由单个表达式(8.5、8.5.3)初始化对象或引用的规则控制。

如果我们交叉引用第 4 条,那么我们知道隐式转换是根据复制初始化定义的(即T t=e;,其中Tintez):

(§4.3) 对于某些发明的临时变量 t (8.5),当且仅当声明 T t=e; 格式正确时,表达式 e 才能隐式转换为类型 T。 [...]

所以我采取 12.3.2:2 不申请 this 初始化,这发生在直接初始化的更大上下文中。否则会与最新的段落相矛盾。

【讨论】:

  • 13.3.1.5 是关于非类类型对象的初始化,但这里y1Y 类型,是类类型,所以我认为这不适用。同样,您引用的 8.5:16 下的项目符号对于目标是类类型的情况是“否则”。
  • @ecatmur 当你失败时,再试一次。现在怎么样?
  • 总而言之,我认为弄清楚标准的实际要求有点令人眼花缭乱,因为第 8 条中的段落引用了第 13 条中的段落,而第 12 条中的段落又引用了第 8 条。但是我我的印象是 12.3.2:2 完全是多余的,可以更改为具有相同效果的非规范。跟踪和交叉引用很有用,但就explicit 的效果而言,8.5 和 13.3.* 是关键。
  • 绝对 12.3.2:2 是一个红鲱鱼或仅适用于非类转换运算符(即第 13.3.1.5 条)。 Jesse Good 找到了一个关于构造函数第一个参数的复制初始化的重要条款,我认为这是大部分答案。
  • @ecatmur 没有类类型在 sn-p 中被复制初始化。但我想我不明白,我不能完全解析“构造函数第一个参数的复制初始化”。
【解决方案2】:

正如 Luc Danton 的回答中所述,隐式转换是根据复制初始化定义的。那么,如果我们看13.3.1.4:1[通过自定义转换复制-初始化类]:

当初始化表达式的类型是类类型“cv S”时, S 的非显式转换函数及其基类是 经过考虑的。当初始化一个临时绑定到第一个 构造函数的参数,它可能引用 cv 限定的 T 作为它的第一个参数,用单个参数调用 直接初始化的上下文,显式转换函数 也被考虑。那些没有隐藏在 S 中并产生一个 其 cv 非限定版本是 与 T 相同的类型或者是 其派生类是候选函数。转换函数 返回“对 X 的引用”返回左值或 x 值,具体取决于 类型 X 的引用类型,因此被认为产生 X 表示选择候选函数的过程。

如果我理解正确,第一个有效,因为转换函数产生Y,因此是引用中第二个强调部分指出的候选函数,但是,在第二种情况下,候选函数集functions 是空的,因为没有到Y 的转换函数,也没有第一个强调部分指出的非显式转换函数。

关于第三种情况:

在找到defect report 1087 之后,您提到的直接初始化 cv2 T 的对象时,其意图似乎是允许、复制、移动和模板构造函数。 如果您阅读 13.3.1.4 的第一段,它会显示 Assuming that “cv1 T” is the type of the object being initialized, with T a class type,所以我认为这意味着您提到的 of an object of type "cv2 T" 但是,(在阅读之后),似乎变化由于缺陷报告导致措辞变得模糊,没有涵盖您提出的第三种情况。

【讨论】:

  • 这里的拷贝初始化是针对Y的构造函数的第一个参数;说“[...] 还考虑显式转换函数”的语言是允许第一种情况的关键(其中 S 是 Z,T 是 const Y &)。第二种情况,这个子句不适用,因为被初始化的对象是Y的构造函数的int参数,所以13.3.1.5适用。但是,这里有一个问题;如果T 是另一个类X 怎么办?
  • @ecatmur:见defect report 1087。他们更改了措辞以允许移动构造函数和模板构造函数,但同时它们使其更加模棱两可,并且不涵盖您提出的情况。
  • @ecatmur:虽然,再读一遍,Assuming that “cv1 T” is the type of the object being initialized 似乎排除了你的第三种情况。
  • 很遗憾,“正在初始化的对象”是Y的构造函数的“临时绑定到第一个参数”,在第三种情况下仍然是X。你完全正确,缺陷报告 1087 是这种歧义悄悄出现的地方。
  • @ecatmur:啊,多读几遍这段话,我同意它不包括你提出的情况。介意提交缺陷报告吗?
【解决方案3】:

我不是语言律师,但是标准的措辞对我来说意味着将转换运算符标记为explicit 要求您明确指定转换类型(即int)作为对象@987654323 初始化的一部分@。使用代码Y y1(z),您似乎依赖于隐式转换,因为您为变量y1 指定的类型是Y

因此,我希望在这种情况下正确使用显式转换运算符:

Y y1( int(z) ); 

或者,由于您正在有效地指定演员表,最好

Y y1( static_cast<int> (z) ); 

【讨论】:

  • 您已经说服了我该标准的意图;由于我们在这里从事语言律师,因此我们只需要弄清楚如何按照其意图阅读该标准。
猜你喜欢
  • 2020-01-26
  • 1970-01-01
  • 2020-09-23
  • 2017-02-13
  • 1970-01-01
  • 2013-12-27
  • 1970-01-01
  • 2011-01-27
  • 2012-03-28
相关资源
最近更新 更多