Consider this code,
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);
}
This compiles fine, runs fine. But if I change f(B)
to f(B&)
, it doesn't compile. If I write f(const B&)
, it again compiles fine, runs fine. Why is the reason and rationale?
Summary:
void f(B); //okay
void f(B&); //error
void f(const B&); //okay
I would like to hear reasons, rationale and reference(s) from the language specification, for each of these cases. Of course, the function signatures themselves are not incorrect. Rather A
implicitly converts into B
and const B&
, but not into B&
, and that causes the compilation error.
No. A reference is simply an alias for an existing object. const is enforced by the compiler; it simply checks that you don't attempt to modify the object through the reference r .
The important difference is that when passing by const reference, no new object is created. In the function body, the parameter is effectively an alias for the object passed in. Because the reference is a const reference the function body cannot directly change the value of that object.
A const reference is actually a reference to const. A reference is inherently const, so when we say const reference, it is not a reference that can not be changed, rather it's a reference to const. Once a reference is bound to refer to an object, it can not be bound to refer to another object.
If the thing you are returning by reference is logically part of your this object, independent of whether it is physically embedded within your this object, then a const method needs to return by const reference or by value, but not by non-const reference.
I would like to hear reasons, rationale and reference(s) from the language specification
Is The Design and Evolution of C++ sufficient?
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 thedouble
passed so a temporary was generated to hold anint
initialized byss
's value. Thus,incr()
modified the temporary, and the result wasn't reflected back to the calling function [emphasis mine].
Think about it: The whole point of call-by-reference is that the client passes things that are changed by the function, and after the function returns, the client must be able to observe the changes.
The problem is that the implicit conversion from a to a B object yields an rvalue. Non-const references can only bind to lvalues.
If B had a default constructor you would get the same behavior if you change the f(a)
call to f(B())
.
--
litb provides a great answer to what is an lvalue: Stack Overflow - often used seldom defined terms: lvalue
GotW #88: A Candidate For the “Most Important const”
Stack Overflow - How come a non-const reference cannot bind to a temporary object?
--
To explain with references to the standard how those function calls fail or succeed would be excessively long. The important thing is how B& b = a;
fails while const B& b = a;
does not fail.
(from draft n1905)
A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:
- [is an lvalue and is either reference compatible or implicitly convertible to an lvalue of a reference compatible type...]
- Otherwise, the reference shall be to a non-volatile const type (i.e., cv1 shall be const).
Here's a case where something is convertible to an lvalue of reference compatible type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With