C++隐式转换左值赋值

15

考虑这段 C++ 代码:

struct Foo {
    float value;

    operator float& () {
        return this->value;
    }
};

int main() {
    Foo foo;
    foo=1.0f;   //Doesn't compile, foo isn't implicitly converted to a float&

    return 0;
}

为什么这个无法编译?C++标准中没有包含它的具体原因吗?或者等价物确实存在,只是我使用不正确?

1
“不起作用,foo 没有隐式转换为 float&。” 你没有告诉它要转换为 float&,你试图将 float 赋值给它(实际上是 double,但这不重要)。这是两件不同的事情。 - ildjarn
4
“@AndersK.的问题实际上是在问为什么它不能编译。所以,当然无法编译。” - Barry
3
@AndersK. 不是这样吗?它仍然可以再现。 - Hatted Rooster
6
@AndersK。不,坦率地说,你的想法有些荒谬。一份不能编译但准确重现了问题提问人所询问的编译器错误的代码,完全超出了关闭该问题的原因所涵盖的范围和意图。 - user743382
2
@AndersK.:别这样了。 - Lightness Races in Orbit
显示剩余6条评论
2个回答

9
几乎所有其他运算符,您的转换运算符将完全按照您的意愿执行,并且即使添加自定义运算符,它也将继续按照您的意愿执行。
struct Foo {
    float value;
    operator float& () { return this->value; }
    Foo &operator+=(Foo);
};
int main() {
    Foo foo {};
    foo+=1.0; // compiles and works
    // equivalent to foo.operator float&()+=1.0;
}

然而,=是特殊的,与大多数其他运算符相比,=的规则不同。正如T.C.所指出的那样:

13.3.1.2 表达式中的运算符 [over.match.oper]

4 对于内置的赋值运算符,左操作数的转换受以下限制:
(4.1) -- 不引入临时对象来保存左操作数,和
(4.2) -- 不对左操作数应用用户定义的转换以使其类型匹配内置候选者的最左参数。

与任何定制的 operator= 不允许被定义为全局函数的事实一起,这确保了当 foo 是类类型时,foo=bar; 总是意味着 foo.operator=(bar); ,没有其他意思。
虽然这个运算符被单独挑选出来并不能解释原因,但它确实清楚地表明这是一个有意的决定,并且仅通过确保 foo=bar; 总是意味着 foo.operator=(bar); ,这本身似乎已经是一个有效的理由了。

我认为“赋值运算符”既包括简单的赋值运算符,也包括复合赋值运算符。 - M.M
“内置的operator=全局函数”是什么意思? - M.M
@T.C. 在那个部分中,“内置赋值运算符”是否包括复合赋值运算符?并且,当该部分的表格显示前者被转换为后者以进行重载决议时,为什么在hvd的代码中foo+=1.0;被接受而foo.operator+=(1.0);被拒绝? - M.M
@M.M,不存在“简单赋值运算符”和“复合赋值运算符”,只有“赋值运算符”和“复合赋值运算符”,请参见[expr.ass]。 - user743382
@T.C. 谢谢你,我找不到明确的东西。我会进行编辑。 - user743382
显示剩余3条评论

6
隐式转换仅在以下情况下进行:
当某个类型T1的表达式在接受某些其他类型T2但不接受该类型的上下文中使用时,会执行隐式转换;特别地,在以下情况下会执行隐式转换:
- 当表达式用作调用以T2为参数声明的函数的参数时; - 当表达式用作需要T2的运算符的操作数时; - 当初始化类型为T2的新对象时,包括在返回T2的函数中的return语句; - 当表达式在switch语句中使用(T2是整数类型); - 当表达式在if语句或循环中使用(T2是bool类型)。
这里没有任何一种情况。相反,编译器正在尝试找到适合与double一起使用的operator=。要使其编译,您需要重载该运算符(实际上您想要一个float,如代码所示)。
Foo& operator=(float other)
{
  value = f;
  return *this;
}

请将您的赋值更改为foo = 1.0f;

您的转换函数可以与以下示例一起使用:

float f = foo;

1
你也可以使用 static_cast<float&>(foo) = 1.0f; 显式地调用转换,但这会稍微有点违背初衷。 - GManNickG
嗯,在那个赋值运算符中你需要一个 return。另外,由于它是内置类型,我会按值传递参数。 - Cheers and hth. - Alf
3
在这个答案中缺少的是为什么无法找到全局内置的operator=。根据[over.built]p18,内置运算符作为float & operator =(float&,double)参与重载决议。在其他运算符中,即使成员运算符函数也可用,它也会被找到和考虑。例如,foo += 1.0;可以正常工作。即使向Foo添加void operator+=(Foo){},它仍然有效:重载决议更喜欢内置运算符。它将被“当表达式用作带有期望T2的运算符的操作数”所覆盖。 - user743382
@hvd:内置运算符参与重载决议,但添加 void operator+=(double) = delete; 会使代码无法编译,因此我不会说内置运算符是首选。 - Cheers and hth. - Alf
那个项目符号列表只是一些例子,它并不打算是一个详尽的列表。你必须具体看重载决议,才能发现在这种情况下它不考虑转换函数。 - M.M
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接