下面的这段代码取消了对 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)
callbool(f.operator int&())
and notf.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)
callf.operator bool()
as intended without markingoperator 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 “cvS
” is the type of the initializer expression, withS
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 withinS
and yield typeT
or a type that can be converted to typeT
via a standard conversion sequence are candidate functions. [...] Conversion functions that return “reference to cv2X
” return lvalues or xvalues, depending on the type of reference, of type “cv2X
” and are therefore considered to yieldX
for this process of selecting candidate functions.
在这个例子中,简历
T
,被初始化的对象的类型是 bool
,因此两个使用定义的转换函数都是可行的候选函数,因为一个直接产生类型 bool
另一个产生可以转换为类型 bool
的类型通过标准转换序列( int
到 bool
);按照 [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 functionF2
if for all argumentsi
,ICSi(F1)
is not a worse conversion sequence thanICSi(F2)
, and then
- /1.3 for some argument
j
,ICSj(F1)
is a better conversion sequence thanICSj(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 ofF2
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 sequenceS2
if
- [...]
- /3.2.6
S1
andS2
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 byS2
refers is more cv-qualified than the type to which the reference initialized byS1
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/