3

后标准草案 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 拒绝从ZYvia的转换是正确的int,还是标准确实允许这种用法?

我考虑了提到的直接初始化的上下文;根据 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
}

根据13.3.1.4:1,使用as和asX将绑定到const X &构造函数的单个参数的临时初始化在直接初始化上下文中进行。我认为该条款不正确,应改为:YTXSZ

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

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

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

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

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

对以上有什么意见吗?

4

3 回答 3

4

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

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

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

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

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

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

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

于 2012-09-11T16:08:24.740 回答
3

正如 Luc Danton 的回答中所指出的,隐式转换是根据复制初始化定义的。然后,如果我们看 13.3.1.4:1[通过用户定义的转换复制-初始化类]:

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

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

关于第三种情况:

在找到缺陷报告 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"您提到了。但是,(阅读后),似乎由于缺陷报告引起的更改导致措辞变得模糊,并没有涵盖您提出的第三种情况。

于 2012-09-11T23:56:31.793 回答
2

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

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

Y y1( int(z) ); 

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

Y y1( static_cast<int> (z) ); 
于 2012-09-11T18:12:22.940 回答