4

我试图弄清楚为什么以下代码段调用 LValue 强制转换运算符重载:

#include <iostream>

class Foo
{
public:
    Foo(int i = 0) : i(i) {}

    operator const int& () const &
    {
        std::cout << "lvalue\n";
        return i;
    }
    
    operator int () const &&
    {
        std::cout << "rvalue\n";
        return i;
    }
    
    int i = 0;
};

Foo Fool()
{
    return Foo(5);
}

int main()
{
    const int& i = Fool();
    const int j = Fool();
    
    return 0;
}

当前的输出是:

左值

右值

但是根据我的理解Fool()返回一个rvalueand 因为const&可以绑定到rvalues没有必要构造一个lvalue Foo.

谁能解释为什么lvalue要建造?我相信这是一个悬空lvalue

4

1 回答 1

1

好的,所以这里要注意的是,重载决议只考虑一个转换函数i。它们不都参与,因此不能使用引用限定符来区分它们。对于绑定参考的情况

[over.match.ref]

在 [dcl.init.ref] 中指定的条件下,引用可以直接绑定到将转换函数应用于初始化表达式的结果。重载分辨率用于选择要调用的转换函数。假设“reference to cv1 T”是被初始化的引用的类型,“cv S”是初始化表达式的类型,其中S是类类型,候选函数选择如下:

  • 考虑了 S 及其基类的转换函数。那些未隐藏在 S 中并产生类型“对 cv2 T2 的左值引用”(初始化对函数的左值引用或右值引用时)或“cv2 T2”或“对 cv2 T2 的右值引用”(当初始化对函数的右值引用或左值引用),其中“cv1 T”与“cv2 T2”引用兼容,是候选函数。对于直接初始化,那些未隐藏在 S 中并产生类型“对 cv2 T2 的左值引用”的显式转换函数(在初始化左值引用时)或对函数的右值引用)或“对 cv2 T2 的右值引用”(在初始化对函数的右值引用或左值引用时),其中 T2 与 T 的类型相同或可以通过限定转换转换为类型 T,是也是候选函数。

根据粗体字,在初始化 时i,我们唯一的候选者是operator int const&。所以重载决议可以在这里通过,或者完全失败。但它不能 select operator int,因为它甚至不在考虑范围内。它成功了,因为 const 限定的左值引用可以绑定到对象参数。

另一方面,用于初始化一个值

[over.match.conv]

在 [dcl.init] 中指定的条件下,作为非类类型对象初始化的一部分,可以调用转换函数将类类型的初始化表达式转换为正在初始化的对象的类型。重载分辨率用于选择要调用的转换函数。假设“cv1 T”是被初始化对象的类型,“cv S”是初始化表达式的类型,其中S是类类型,候选函数选择如下:

  • 考虑了 S 及其基类的转换函数。那些不隐藏在 S 中并产生类型 T 或可以通过标准转换序列转换为类型 T 的类型的非显式转换函数是候选函数。对于直接初始化,那些不隐藏在 S 中并产生类型 T 或可以通过限定转换转换为类型 T 的类型的显式转换函数也是候选函数。返回 cv 限定类型的转换函数被认为会为选择候选函数的过程产生该类型的 cv 非限定版本。对返回“对 X 的引用”的转换函数的调用是 X 类型的左值,因此这种转换函数被认为会在选择候选函数的过程中产生 X。

因此,在初始化j两个转换函数时,它们都作为重载参与,这里引用限定符会有所不同。

您确实在这里得到了一个悬而未决的参考,这似乎是由于语言中的一个黑暗角落。第一个引用段落中的项目符号可能会被改进以更好地考虑const lvlaue 引用的绑定。由于这些也可能绑定到临时变量,因此您的第二个转换运算符理想情况下可能是更好规则下的候选对象。

于 2020-02-27T06:48:35.973 回答