标准后草案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是否正确拒绝通过Z
从Y
转换为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 Y
和operator 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
构造函数的单个const X &
参数的临时Y
的初始化在每个13.3.1.4:1的直接初始化上下文中进行,{ {1}}为T
,X
为S
。我认为这个条款不正确,应该是:
13.3.1.4通过用户定义的转换复制初始化类[over.match.copy]
1 - [...]初始化临时以绑定到第一个参数 引用可能 cv -qualified
Z
作为其第一个参数的构造函数,在类型为“的对象的直接初始化的上下文中使用单个参数调用cv2T
“,也考虑了显式转换函数。 [...]
为避免混淆,我认为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)。 [...]
对上述内容有何评论?
答案 0 :(得分:4)
根据8.5和13.3.1.3,考虑Y
的构造函数,并通过重载决策选择最佳构造函数。在这种情况下,相关的构造函数是Y(int);
以及复制和移动构造函数。在重载决策过程中,13.3.2 Viable functions [over.match.viable]指定了这个:
3其次,要使
F
成为一个可行的函数,每个参数都应存在一个隐式转换序列(13.3.3.1),它将该参数转换为{的相应参数{1}}。 [...]
对于所有这些构造函数,没有从F
转换为Z
或int
的其中一种转换。为了说服自己,让我们来研究标准中关于13.3.3.1隐式转换序列中隐式转换序列的内容[over.best.ics]:
1隐式转换序列是一系列转换,用于将函数调用中的参数转换为被调用函数的相应参数的类型。转换序列是第4章中定义的隐式转换,这意味着它由单个表达式(8.5,8.5.3)初始化对象或引用的规则控制。
如果我们交叉引用第4章,那么我们就会知道隐式转换是根据复制初始化来定义的(即Y
,其中T t=e;
是T
和{{1是} int
):
(§4.3)当且仅当声明
e
格式正确时,表达式e才能隐式转换为类型T,对于某些发明的临时变量t(8.5)。 [...]
所以我采用12.3.2:2不申请这个初始化,这发生在直接初始化的更大的上下文中。否则会与最新段落相矛盾。
答案 1 :(得分:3)
正如Luc Danton的回答所述,隐式转换是根据复制初始化来定义的。然后,如果我们看一下13.3.1.4:1 [复制 - 通过用户定义的转换初始化类]:
当初始化表达式的类型是类类型“cv S”时, S 的非显式转换函数及其基类 考虑。初始化临时时绑定到第一个 可能引用的构造函数的参数 cv-qualified T作为它的第一个参数,用一个参数调用 直接初始化,显式转换函数的上下文 也被认为。那些没有隐藏在S中并产生一个的东西 其cv-nonqualified版本是与T相同的类型或者是a 派生类是候选函数。转换功能 返回“引用X”返回左值或x值,具体取决于 类型为X的引用类型因此被认为是屈服的 X用于选择候选函数的过程。
如果我理解正确,那么第一个就可以了,因为转换函数会产生一个Y
,因此它是一个候选函数,如引用中第二个强调部分所述,但是,在第二种情况下,该集合候选函数是空的,因为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"
。
答案 2 :(得分:2)
我不是语言律师,但是标准的措辞意味着我将转化运算符标记为explicit
要求您明确指定转换类型(即int
)作为初始化的一部分对象y1
。使用代码Y y1(z)
,您似乎依赖于隐式转换,因为您为变量y1
指定的类型为Y
。
因此,我希望在这种情况下正确使用显式转换运算符:
Y y1( int(z) );
或者,因为你有效地指定一个演员,最好是
Y y1( static_cast<int> (z) );