隐式转换:const引用 vs 非const引用 vs 非引用

11
考虑这段代码,
struct A {};
struct B {  B(const A&) {} };
void f(B)
{
    cout << "f()"<<endl;
}
void g(A &a)
{
    cout << "g()" <<endl;
    f(a); //a is implicitly converted into B.
}
int main()
{
    A a;
    g(a);
}

这个程序编译和运行都没问题。但如果我将 f(B) 改成 f(B&),它就不能编译。如果我写成 f(const B&),它又会正常编译和运行。原因和逻辑是什么?
void f(B);         //okay
void f(B&);        //error
void f(const B&);  //okay

我希望听到语言规范中针对这些情况的原因、理由和参考资料。当然,函数签名本身并没有错误。相反,A 隐式转换为 Bconst B&,但不是 B&,这导致了编译错误。
2个回答

9

我希望从语言规范中听到理由、基本原理和参考资料。

《C++设计与演化》足够吗?

I made one serious mistake, though, by allowing a non-const reference to be initialized by a non-lvalue [comment by me: that wording is imprecise!]. For example:

void incr(int& rr) { ++rr; }

void g()
{
    double ss = 1;
    incr(ss);    // note: double passed, int expected
                 // (fixed: error in release 2.0)
}

Because of the difference in type the int& cannot refer to the double passed so a temporary was generated to hold an int initialized by ss's value. Thus, incr() modified the temporary, and the result wasn't reflected back to the calling function [emphasis mine].

想一想:按引用传递的整个重点在于客户端传递的是由函数修改的内容,在函数返回后,客户端必须能够观察到这些变化

一个有趣的事情:在ARM中,对于primary-expression的lvalueness的确定如下:“如果标识符是,则结果是lvalue。”..“如果成员是,则结果是lvalue。”我一直想知道这到底意味着什么,因为它也定义了“lvalue是指引用对象或函数的表达式”。嗯,也许它只是意味着“如果它引用对象或函数,则结果是lvalue”? - Johannes Schaub - litb
这很好。我认为它更好地回答了我的问题,因为它解释了为什么不允许这样做。我接受这个作为我的问题的答案。 :-) - Nawaz

4
问题在于从a到B对象的隐式转换会产生一个rvalue。非const引用只能绑定到lvalue。
如果B有默认构造函数,那么将f(a)调用更改为f(B())将得到相同的行为。

--

litb提供了一个关于lvalue的很好的解释:Stack Overflow - 经常使用但很少定义的术语:lvalue

GotW #88: “最重要的const”候选项

Stack Overflow - 为什么非const引用不能绑定到临时对象?

--

解释如何通过标准来说明这些函数调用失败或成功将会过于冗长。重要的是,B& b = a; 失败而 const B& b = a; 不会失败。
(来自草案 n1905)
引用类型“cv1 T1”通过类型为“cv2 T2”的表达式进行初始化,如下所示: - [是左值且要么与引用兼容,要么可以隐式转换为引用兼容类型...] - 否则,该引用应为非易失性 const 类型(即 cv1 应为 const)。
这里有一个情况(此处),其中某个东西可以转换为引用兼容类型的左值。

为什么@Nawaz的问题没有回答了23分钟,而当我回答时,你在30秒之前提供了一个答案。这太神奇了!哈哈,+1。 - Johannes Schaub - litb
@Chris:请同时引用相关参考资料,这样我就可以自己探索相关概念了。 - Nawaz
3
严格来说,您根本无法创建lvalue或rvalue,因为值类别是表达式(编译时)的属性,而不是对象(运行时)的属性。 - fredoverflow
@Fred,没错。我将“conversion...creates an rvalue”改为了“conversion...yields an rvalue”,这是我所拥有的草案标准中使用的术语。 - Chris Hopman
很棒的答案,看到进一步解释的引用非常鼓舞人心。 - j_random_hacker

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