c++ - 为什么 bool(val) 比 val.operator bool() 更喜欢双重隐式转换?

标签 c++ type-conversion operator-overloading language-lawyer implicit-conversion

下面的这段代码取消了对 nullptr 的引用.

struct Foo {
    int *bar;
    operator int&() {
        return *bar;
    }
    explicit operator bool() const {
        return bar;
    }
};
int main() {
    Foo f {nullptr};
    auto _ = bool(f); 
}
  • 为什么bool(f)调用 bool(f.operator int&())而不是 f.operator bool()正如预期的那样?
  • 有没有办法让bool(f)调用 f.operator bool()无标记 operator int&explicit ?
  • 最佳答案

    在用户定义的转换的重载解析中,参数转换排名优先于返回类型转换排名
    以下所有标准引用均指 N4659: March 2017 post-Kona working draft/C++17 DIS .

    准备工作
    为了避免处理段错误,auto explicit的类型推导和考虑标记的转换函数,请考虑以下示例的简化变体,它显示了相同的行为:

    #include <iostream>
    
    struct Foo {
        int bar{42};
        operator int&() {
            std::cout << __PRETTY_FUNCTION__;
            return bar;
        }
        operator bool() const {
            std::cout << __PRETTY_FUNCTION__;
            return true;
        }
    };
    
    int main() {
        Foo f {};
        // (A):
        bool a = f;  // Foo::operator int&()
    }
    

    TLDR

    Why does bool(f) call bool(f.operator int&()) and not f.operator bool() as was intended?


    初始化转换序列的可行候选函数是
    operator int&(Foo&)     
    operator bool(const Foo&) 
    
    [over.match.best]/1.3 ,在函数参数上排名,优先于 [over.match.best]/1.4 , 从返回类型转换的排名,意思是 operator int&(Foo&)对于 Foo& 类型的参数,将被选择,因为它是一个非歧义的完美匹配。 ,根据 [over.match.best]/1.3 规则,而 operator bool(const Foo&)不是。
    在这方面,由于我们依赖于 [over.match.best]/1.3,这与仅在 const 上简单重载没有什么不同。 -资格:
    #include <iostream>
    
    struct Foo {};
    
    void f(Foo&) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    void f(const Foo&) { std::cout << __PRETTY_FUNCTION__ << "\n"; }
    
    int main() {
        Foo f1 {};
        const Foo f2{};
        f(f1); // void f(Foo&)
        f(f2); // void f(const Foo&)
    }
    

    Is there a way to make bool(f) call f.operator bool() as intended without marking operator int& as explicit?


    如上所述,如果您为成员函数重载提供匹配的 cv 限定符,则根据 [over.match.best]/1.3 和 bool,隐式对象参数不再有任何区别。根据 [over.match.best]/1.4,将选择最可行的转换函数。请注意,通过标记 int&转换函数为 explicit ,它将不再是一个可行的候选者,而是 bool 的选择。转换函数不是因为它是最可行的重载,而是因为它是唯一可行的重载。

    细节
    (A) 处的表达式是初始化,语义由 [dcl.init]/17.7 专门控制。 [提取,重点 矿]:

    [dcl.init]/17 The semantics of initializers are as follows. The destination type is the type of the object or reference being initialized and the source type is the type of the initializer expression.

    • [...]
    • /17.7 Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated ([over.match.conv]), and the best one is chosen through overload resolution. The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. [...]

    哪里[over.match.conv]/1描述哪些转换函数被视为重载决议中的候选函数:

    [over.match.conv]/1 Under the conditions specified in [dcl.init], as part of an initialization of an object of non-class type, a conversion function can be invoked to convert an initializer expression of class type to the type of the object being initialized. Overload resolution is used to select the conversion function to be invoked. Assuming that “cv1 T” is the type of the object being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

    • /1.1 The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence are candidate functions. [...] Conversion functions that return “reference to cv2 X” return lvalues or xvalues, depending on the type of reference, of type “cv2 Xand are therefore considered to yield X for this process of selecting candidate functions.

    在这个例子中,简历 T ,被初始化的对象的类型是 bool ,因此两个使用定义的转换函数都是可行的候选函数,因为一个直接产生类型 bool另一个产生可以转换为类型 bool 的类型通过标准转换序列( intbool );按照 [conv.bool] :

    A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. [...]


    此外,初始化表达式的类型是 Foo , 和 [over.match.funcs]/4控制隐式对象类型的 cv 限定 参数 对于用户定义的转换函数,是相应函数的 cv 限定:

    For non-static member functions, the type of the implicit object parameter is

    • “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
    • [...]

    where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration. [...] For conversion functions, the function is considered to be a member of the class of the implied object argument for the purpose of defining the type of the implicit object parameter. [...]


    因此,w.r.t.重载决议我们可以总结如下:
    // Target type: bool
    // Source type: Foo
    
    // Viable candidate functions:
    operator int&(Foo&)     
    operator bool(const Foo&) 
    
    在继续讨论重载决议如何选择最佳可行候选者时,我们在不失一般性的情况下添加了隐含对象参数作为显式函数参数(根据 [over.match.funcs]/5)。
    现在,[over.ics.user] ,特别是 [over.ics.user]/2总结一下:

    [over.ics.user]/2 [...] Since an implicit conversion sequence is an initialization, the special rules for initialization by user-defined conversion apply when selecting the best user-defined conversion for a user-defined conversion sequence (see [over.match.best] and [over.best.ics]).


    特别是选择最佳可行候选者的规则由 [over.match.best] 管理。 ,特别是 [over.match.best]/1 :

    [over.match.best]/1 [...] Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

    • /1.3 for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,
    • /1.4 the context is an initialization by user-defined conversion (see [dcl.init], [over.match.conv], and [over.match.ref]) and the standard conversion sequence from the return type of F1 to the destination type (i.e., the type of the entity being initialized) is a better conversion sequence than the standard conversion sequence from the return type of F2 to the destination type

    这里的关键是 [over.match.best]/1.4,关于候选返回类型(到目标类型)的转换仅适用于无法通过 [over.match.best]/1.3 消除重载的歧义.然而,在我们的例子中,回想一下可行的候选函数是:
    operator int&(Foo&)     
    operator bool(const Foo&)
    
    根据 [over.ics.rank]/3.2 ,特别是 [over.ics.rank]/3.2.6 :

    [over.ics.rank]/3 Two implicit conversion sequences of the same form are indistinguishable conversion sequences unless one of the following rules applies:

    • [...]
    • /3.2 Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
      • [...]
      • /3.2.6 S1 and S2 are reference bindings ([dcl.init.ref]), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

    意思是,对于 Foo& 类型的参数, operator int&(Foo&)将是一个更好的(/1.3:精确)匹配,而例如const Foo& 类型的参数, operator bool(const Foo&)将唯一匹配( Foo& 将不可行)。

    关于c++ - 为什么 bool(val) 比 val.operator bool() 更喜欢双重隐式转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64838687/

    相关文章:

    c++ - operator<< 何时指代插入操作符,何时指代按位左移?

    java - 为什么我可以将 int 数据类型中的十进制数字添加到字符串中,而无需进行数据类型转换

    vb.net - 如何以长整数形式存储二进制补码

    c++ - 将 struct 转换为 unsigned char *

    c++ - QDataStream : Overloading >> for decorated QVector

    c++ - 使用 >> 运算符重载时出错

    c++ - 在派生类中重载新运算符

    c++ - valgrind 未处理的指令字节 : 0xF 0xB 0xFF 0x85

    c++ - 错误重新: inheritance of private members?

    c++ - 访问单例类 C++ 时遇到问题