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

Why does bool(val) prefer double implicit conversions over val.operator bool()?

下面的代码片段取消了对 nullptr.

的引用
struct Foo {
    int *bar;
    operator int&() {
        return *bar;
    }
    explicit operator bool() const {
        return bar;
    }
};
int main() {
    Foo f {nullptr};
    auto _ = bool(f); 
}
  1. 为什么 bool(f) 调用 bool(f.operator int&()) 而不是预期的 f.operator bool()
  2. 有没有办法让 bool(f) 按预期调用 f.operator bool() 而无需 operator int& 标记为 explicit

参数转换排名优先于 return 在用户定义转换的重载决策中类型转换排名

以下所有标准参考均指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, ranking on function arguments, takes precedence over [over.match.best]/1.4,根据 return 类型的转换排名,这意味着 operator int&(Foo&) 将被选择,因为它是一个无歧义的完美匹配,对于类型 [=21] 的参数=],根据 [over.match.best]/1.3 规则,而 operator bool(const Foo&) 不是。

在这方面,由于我们依赖 [over.match.best]/1.3,这与仅在 const-qualification:

上简单地重载没有什么不同
#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,它将不再是 viable 候选,并且选择 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.

在这个例子中,cv T,被初始化对象的类型是bool,因此两个使用定义的转换函数都是viable 候选者,因为一个直接产生类型 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], particularly [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], particularly [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,关于候选return类型(到目标类型)的转换只适用 如果重载不能通过 [over.match.best]/1.3 消除歧义。然而,在我们的示例中,请记住可行的候选函数是:

operator int&(Foo&)     
operator bool(const Foo&)

根据[over.ics.rank]/3.2, particularly [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& 将不可行)。