const成员函数优先于返回值类型匹配 [英] Precedence of const member function over return value type match

查看:104
本文介绍了const成员函数优先于返回值类型匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

中,Y :: test1()优先使用非常数 X :: operator void *()看起来更好的匹配, X :: operator bool()const - 为什么?

  #include< iostream> 

struct X {
operator void *(){std :: cout< operator void *()\\\
; return nullptr; }
operator bool()const {std :: cout<< operator bool()\\\
; return true; }
};

struct Y {
X x;
bool test1(){std :: cout<< test1()\\\
; return x; }
bool test2()const {std :: cout<< test2()\\\
; return x; }
};

int main(){
Y y;
y.test1();
y.test2();
}

输出:

  test1()
operator void *()
test2()
operator bool()
pre>

解决方案

首先:当转换中的表达式返回语句到函数的返回类型,规则与初始化相同(见[conv] /2.4和[conv] / 3)。



使用此示例代替(与您拥有相同的 X ,但没有 Y )的代码的行为:

  X test1; 
bool b1 = test1;

X const test2;
bool b2 = test2;

(在 y.test2() this-> x 的类型是 X const ,这就意味着有一个const成员函数)。如果我们转换到 bool ,而不是写一个初始化语句,也是一样。






在这种情况下,处理重载分辨率的部分是[over.match.conv],这里是C ++ 14文本(为了简洁起见,有些省略):


13.3.1.5通过转换函数[over.match.conv]初始化



1 在8.5中指定的条件下,作为非类型对象初始化的一部分,可以调用转换函数将类类型的初始化器表达式转换为对象的类型初始化。 [...]



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


test2 是简单的 - 一个非const成员函数不能在一个const对象上调用,所以从不考虑 operator void * ,只有一个候选,不需要重载分辨率。 operator bool()被调用。



所以对于这篇文章的其余部分,我只是谈论 test1 情况。在上面的引用中省略的部分涵盖了 operator bool operator void *() test1 案例。






请务必注意,重载解析选择这两个候选函数考虑两个隐式转换序列的情况,每个转换序列包含用户定义的转换。为了支持这一点,看看[over.best.ics]的第一句:


隐式转换序列

em>是用于将函数调用中的参数转换为正在调用的函数的相应参数的类型的转换序列。


我们不是在函数调用中转换参数。关于隐式转换序列的规则在我们对候选函数进行排序时起作用:这些规则被应用于每个候选函数的每个参数,我们稍后会看到。






现在我们来看看最佳可行函数的规则,以确定选择这两个候选函数中的哪一个。我将跳过[over.match.viable],这说明这两个候选都是可行的,并且在[over.match.best]上。



该部分的关键部分是[over.match.best] /1.2:


let ICSi(F)表示将列表中的第i个参数转换为可行函数F的第i个参数的类型的隐式转换序列。13.3.3.1定义隐式转换序列和13.3.3.2定义了一个隐式转换序列是一个更好的转换序列
或比另一个转换序列更糟糕的意思。


这里, i == 1 ,只有一个参数 test1 .match.conv] / 2上面 - 参数是初始化表达式 test1 ; 可行函数的参数是 X 的成员函数的隐式对象参数。



隐含的转换顺序规则适用:




  • $ c>不需要转换 - 参数为 X ,参数为 X&

  • operator bool()const 需要一个限定转换 - 参数为 X ,参数为 X const&



转换([over.ics.rank] /3.1.1)。所以ICS1( operator void *())是比ICS1( operator bool()const )更好的转换序列;所以此时 operator void *() wins([over.match.best] /1.3)。



接下来的段落[over.match.best] /1.4解释了如果这两个序列都不是更好的话,会发生什么情况:我们只需要将候选函数的返回类型的标准转换序列比较到类型



您可以通过将 X 成员更改为 operator void * ()const 。现在,两个ICS1序列与/1.3是无法区分的,所以我们进入/1.4,此时运算符bool()const wins,因为它转换为 bool 是标识,而 operator void *()const 仍需要布尔转换。


In Y::test1() a non-const X::operator void*() takes precedence over a seemingly better match, X::operator bool() const - Why is that? And where is this phenomenon described in the standard?

#include <iostream>

struct X {
  operator void*()      { std::cout << "  operator void*()\n"; return nullptr; }
  operator bool() const { std::cout << "  operator bool()\n";  return true; }
};

struct Y {
  X x;
  bool test1()       { std::cout << "test1()\n"; return x; }
  bool test2() const { std::cout << "test2()\n"; return x; }
};

int main() {
  Y y;
  y.test1();
  y.test2();
}

Output:

test1()
  operator void*()
test2()
  operator bool()

解决方案

First of all: when converting the expression in a return statement to the return type of the function, the rules are the same as for initialization (see [conv]/2.4 and [conv]/3).

So we could examime the behaviour of the code using this example instead (with the same X as you have, but without Y):

X test1;
bool b1 = test1;

X const test2;
bool b2 = test2;

(in the call y.test2(), the type of this->x is X const, that's what it means to have a const member function). It would also be the same if we cast to bool instead of writing an initialization statement.


The part of the Standard dealing with overload resolution in this situation is [over.match.conv], here is the C++14 text (with some elision for brevity):

13.3.1.5 Initialization by conversion function [over.match.conv]

1 Under the conditions specified in 8.5, as part of an initialization of an object of nonclass type, a conversion function can be invoked to convert an initializer expression of class type to the type of the object being initialized. [...]

2 The argument list has one argument, which is the initializer expression. [Note: This argument will be compared against the implicit object parameter of the conversion functions. —end note ]

The test2 case is straightforward - a non-const member function cannot be called on a const object, so the operator void* is never considered, there is only one candidate and no need for overload resolution. operator bool() is called.

So for the rest of this post I will just talk about the test1 case. The part I elided in the above quote covers that both operator bool and operator void*() are candidate functions for the test1 case.


It is important to note that overload resolution selects amongst these two candidate functions, and it is not a case of considering two implicit conversion sequences, each containing a user-defined conversion. To back this up, look at the first sentence of [over.best.ics]:

An implicit conversion sequence is a sequence of conversions used to convert an argument in a function call to the type of the corresponding parameter of the function being called.

We are not converting an argument in a function call here. The rules about implicit conversion sequences come into play when we are ranking candidate functions: the rules are applied to each argument of each candidate function, as we shall see in a moment.


So now we look to the rules for best viable function to determine which of these two candidate functions is selected. I'll skip [over.match.viable], which clarifies that both of those candidates are viable, and onto [over.match.best].

The key part of that section is [over.match.best]/1.2:

let ICSi(F) denote the implicit conversion sequence that converts the i-th argument in the list to the type of the i-th parameter of viable function F. 13.3.3.1 defines the implicit conversion sequences and 13.3.3.2 defines what it means for one implicit conversion sequence to be a better conversion sequence or worse conversion sequence than another.

Here, i == 1, there is only one argument test1 as explained by [over.match.conv]/2 above -- the argument is the initializer expression test1; the "parameter of the viable function" is the implicit object parameter of the member functions of X.

Now the implicit conversion sequence rules apply:

  • operator void*() requires no conversion - the argument is X and the parameter is X&
  • operator bool() const requires a qualification conversion - the argument is X and the parameter is X const&

No conversion is better than qualification conversion ([over.ics.rank]/3.1.1). So ICS1(operator void*()) is a better conversion sequence than ICS1(operator bool() const); so at this point operator void*() wins ([over.match.best]/1.3).

The subsequent paragraph [over.match.best]/1.4 explains what would have happened if neither of those two sequences was better: we would only then go on to compare the standard conversion sequences from the return type of the candidate function onto the type being initialized.

You can explore this case by changing the X member to operator void*() const. Now the two ICS1 sequences are indistinguishable as of /1.3, so we go onto /1.4 at which point operator bool() const wins because its conversion to bool is the identity, whereas operator void*() const still requires a boolean conversion.

这篇关于const成员函数优先于返回值类型匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆