由不同编译器调用的不同强制转换操作符

buc*_*buc 79 c++ language-lawyer

考虑以下简短的C++程序:

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

如果我在不同的编译器上编译它,我会得到各种结果.使用Clang 3.4和GCC 4.4.7打印true,而Visual Studio 2013打印false,这意味着它们调用不同的强制转换操作符(bool)b.根据标准,这是正确的行为?

在我的理解operator bool()需要转变,而operator int()将需要intbool转换,所以编译器应该选择第一个.是否const做一些与该是由编译器认为更"贵"常量转换?

如果我删除了const,所有编译器同样产生false输出.另一方面,如果我将两个类组合在一起(两个运算符将在同一个类中),则所有三个编译器都将生成true输出.

Rob*_*ahy 51

标准规定:

派生类中的转换函数不会隐藏基类中的转换函数,除非这两个函数转换为相同的类型.

§12.3[class.conv]

这意味着operator bool没有被隐藏operator int.

标准规定:

在重载解析期间,隐含的对象参数与​​其他参数无法区分.

§13.3.3.1[over.match.funcs]

在这种情况下b,"隐含的对象参数" 是类型的B2 &. operator bool需要const B2 &,所以编译器必须添加const b来调用operator bool.这 - 所有其他条件相同 - 可以operator int更好地匹配.

该标准规定a static_cast(在这种情况下,C风格的演员阵容正在执行)可以转换为类型T(在这种情况下int),如果:

T t(e);对于一些发明的临时变量,声明是格式良好的t.

§5.2.9[expr.static.cast]

因此int可以转换为a bool,并且a bool可以同样地转换为a bool.

标准规定:

S考虑转换函数及其基类.那些未隐藏在其中的非显式转换函数S和yield类型T 或可T通过标准转换序列转换为类型的类型是候选函数.

§13.3.1.5[over.match.conv]

所以重载集由operator int和组成operator bool.所有其他条件相同,operator int是一个更好的匹配(因为你不必添加常量).因此operator int应该选择.

注意(可能违反直觉)标准一旦将它们添加到重载集(如上所述),就不会考虑返回类型(即这些运算符转换的类型),前提是其中一个参数的转换序列它们优于另一个参数的转换序列(由于常量,在这种情况下是这种情况).

标准规定:

给定这些定义,可行函数F1被定义为比另一个可行函数F2更好的函数,如果对于所有自变量i,ICSi(F1)不是比ICSi(F2)更差的转换序列,然后

  • 对于某些参数j,ICSj(F1)是比ICSj(F2)更好的转换序列,或者,如果不是,
  • 上下文是通过用户定义的转换初始化,从返回类型F1到目标类型的标准转换序列(即,正在初始化的实体的类型)是比返回类型的标准转换序列更好的转换序列F2到目的地类型.

§13.3.3[over.match.best]

在这种情况下,只有一个参数(隐含this参数).B2 &=> B2 &(调用operator int)的转换序列优于B2 &=> const B2 &(调用operator bool),因此operator int从重载集中选择,而不考虑它实际上不直接转换为bool.

  • 你可以得到N3337,这是(我相信)C++ 11之后的第一个草案,只包括非常非常小的变化[这里](http://www.open-std.org/jtc1/sc22/wg21/ docs/papers/2012/n3337.pdf)免费.至于找到标准的适当部分,通常需要判断PDF的各个部分,了解事物的位置(通过查看/引用标准),并使用CTRL + F搜索相关的关键字. (2认同)
  • 这就是我所说的"仅在确定哪个转换序列更好后才应用".然而,我认为这是答案的一个重要部分:(理论上)隐含对象参数的转换与"返回类型的转换"之间存在冲突,并且通过重载决策的不同步骤明确地解决了它.[over.match.best] /1.4清楚地指出了这些步骤的分离,以及为什么最终的标准转换顺序在这种情况下并不关心. (2认同)
  • 我编辑了答案以包含您的建议,详细说明为什么在选择"最佳"转换操作符时不考虑这些转换操作符的返回类型. (2认同)
  • @RobertAllanHenniganLeahy既然这就是问题的关键,那么它不应该出现在答案的某个地方,而不仅仅是评论吗? (2认同)

Pix*_*ist 9

转换函数operator int()是通过clang选择的,operator bool() const因为b它不是const限定的,而bool的转换运算符是.

短的理由是,对于重载解析候选的功能(在适当位置隐式对象参数),在转换时bbool

operator bool (B2 const &);
operator int (B2 &);
Run Code Online (Sandbox Code Playgroud)

其中第二个是更好的匹配,因为b不是const限定.

如果两个函数共享相同的限定条件(两者都兼有const),operator bool则选择它,因为它提供了直接转换.

通过cast-notation进行转换,逐步分析

如果我们同意调用布尔ostream插入器(std :: basic_ostream :: operator <<(bool val)作为[ostream.inserters.arithmetic])并使用转换为的结果b,bool我们可以深入研究转换.

1.演员表达

b的演员布尔

(bool)b
Run Code Online (Sandbox Code Playgroud)

评估为

static_cast<bool>(b)
Run Code Online (Sandbox Code Playgroud)

根据C++ 11,5.4/4 [expr.cast]因为const_cast不适用(不在这里添加或删除const).

如果发明变量t形成良好,则每个C++ 11,5.2.9/4 [expr.static.cast]允许这种静态转换bool t(b);.根据C++ 11,8.5/15 [dcl.init],这些语句称为直接初始化.

2.直接初始化 bool t(b);

最不提及的标准段落第16条规定(强调我的):

初始化器的语义如下.目标类型是要初始化的对象或引用的类型,源类型是初始化表达式的类型.

[...]

[...]如果源类型是(可能是cv限定的)类类型,则考虑转换函数.

枚举适用的转换函数,并通过重载决策选择最佳函数.

2.1哪些转换功能可用?

可用的转换函数operator int ()operator bool() const自如C++ 11,12.3/5 [class.conv]告诉我们:

派生类中的转换函数不会隐藏基类中的转换函数,除非这两个函数转换为相同的类型.

C++ 11,13.3.1.5/1 [over.match.conv]指出:

考虑S及其基类的转换函数.

其中S是将要转换的类.

2.2哪些转换功能适用?

C++ 11,13.3.1.5/1 [over.match.conv](强调我的):

1 [...]假设"cv1 T"是要初始化的对象的类型,并且"cv S"是初始化表达式的类型,S是类类型,候选函数选择如下:转换考虑S及其基类的函数.那些未隐藏在S和yield类型T中的非显式转换函数或可通过标准转换序列转换为类型T的类型是候选函数.

因此operator bool () const它是适用的,因为它不是隐藏在内B2并产生一个bool.

在最后一个标准引用中强调的部分与转换相关,operator int ()因为int是一种可以通过标准转换序列转换为bool的类型.转换为intto bool甚至不是一个序列,而是一个普通的直接转换,允许每个C++ 11,4.12/1 [conv.bool]

算术,无范围枚举,指针或指向成员类型的指针的prvalue可以转换为bool类型的prvalue.零值,空指针值或空成员指针值转换为false; 任何其他值都转换为true.

这意味着它operator int ()也适用.

2.3选择了哪种转换功能?

通过重载决策(C++ 11,13.3.1.5/1 [over.match.conv])选择适当的转换函数:

重载分辨率用于选择要调用的转换函数.

当涉及类成员函数的重载解析时,有一个特殊的"怪癖":隐式对象参数".

Per C++ 11,13.3.1 [over.match.funcs],

[...]静态和非静态成员函数都有一个隐式对象参数[...]

其中非静态成员函数的此参数的类型 - 根据第4节:

  • 对于没有ref-qualifier或使用&ref-qualifier声明的函数,"对cv X的左值引用"

  • 对于使用&& ref-qualifier声明的函数的"对cv X的rvalue引用"

其中X是函数所属的类,cv是成员函数声明的cv-qualification.

这意味着(根据C++ 11,13.3.1.5 /2 [over.match.conv]),在转换函数的初始化中,

[t]他的参数列表有一个参数,它是初始化表达式.[注意:此参数将与转换函数的隐式对象参数进行比较. - 尾注]

重载解析的候选函数是:

operator bool (B2 const &);
operator int (B2 &);
Run Code Online (Sandbox Code Playgroud)

显然,operator int ()是如果使用类型的非恒定对象所请求的转换更好的匹配B2,因为operator bool ()所需要的资质转换.

如果两个转换函数共享相同的const限定,那么这些函数的重载解析将不再起作用.在这种情况下,转换(序列)排名到位.

3. operator bool ()当两个转换函数共享相同的const限定时,为什么选择?

转换B2bool是用户定义的转换序列(C++ 11,13.3.3.1.2/1 [over.ics.user])

用户定义的转换序列包括初始标准转换序列,然后是用户定义的转换,后跟第二个标准转换序列.

[...]如果转换函数指定了用户定义的转换,则初始标准转换序列会将源类型转换为转换函数的隐式对象参数.

C++ 11,13.3.3.2/3 [over.ics.rank]

[...]基于关系更好的转换序列和更好的转换来定义隐式转换序列的部分排序.

[...]用户定义的转换序列U1是比另一个用户定义的转换序列U2更好的转换序列,如果它们包含相同的用户定义的转换函数或构造函数或聚合初始化,并且U1的第二个标准转换序列优于U2的第二个标准转换序列.

第二标准转化箱子operator bool()boolbool(标识转换),而在情况下,第二标准的转换operator int ()intbool这是一个布尔值转换.

因此,operator bool ()如果两个转换函数共享相同的const限定,则使用转换序列会更好.