11

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.

Nawaz
  • 353,942
  • 115
  • 666
  • 851

2 Answers2

9

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 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].

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.

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • An interesting thing: In the ARM, determination of lvalueness for primary-expression is as follows: "The result is an lvalue if the identifier is." .. "The result is an lvalue if the member is.". I've wondered what the heck that should mean, because it too defines "An lvalue is an expression referring to an object or function.". Hmm, maybe it just means "The result is an lvalue if it refers to an object or function."? – Johannes Schaub - litb Jan 16 '11 at 10:10
  • This is good. I think it better answers my question, as it explains reason why it's not allowed. I'm accepting this as answer to my question. :-) – Nawaz Jan 16 '11 at 16:58
4

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.

Community
  • 1
  • 1
Chris Hopman
  • 2,082
  • 12
  • 11
  • Why is it that @Nawaz's question was unanswered for 23 minutes, and when I anwered it, you provided an answer 30 seconds before me. That's magic! Haha, +1. – Johannes Schaub - litb Jan 16 '11 at 08:34
  • @Chris : Please quote the relevant references also, so that I can explore the relevant concepts myself. – Nawaz Jan 16 '11 at 08:44
  • 3
    Strictly speaking, you cannot create lvalues or rvalues at all, since the value-category is an attribute of *expressions* (compile time), not *objects* (runtime). – fredoverflow Jan 16 '11 at 09:12
  • @Fred, true. Changed "conversion...creates an rvalue" to "conversion...yields an rvalue" which is the terminology in the draft standard that I have. – Chris Hopman Jan 16 '11 at 10:20
  • Great answer, it's encouraging to see references to further explanations. – j_random_hacker Jan 16 '11 at 11:58