显式转换函数,直接初始化和转换构造函数

时间:2012-09-11 14:49:07

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

标准后草案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是否正确拒绝通过ZY转换为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
}

要绑定到X构造函数的单个const X &参数的临时Y的初始化在每个13.3.1.4:1的直接初始化上下文中进行,{ {1}}为TXS。我认为这个条款不正确,应该是:

  

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

     

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

为避免混淆,我认为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)。 [...]

对上述内容有何评论?

3 个答案:

答案 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转换为Zint的其中一种转换。为了说服自己,让我们来研究标准中关于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) );