views:

545

answers:

4

I have the following code which doesn't compile. The compiler error is:

"error: no matching function to call B::B(B)",
candidates are B::B(B&) B::B(int)"

The code compiles under either of the following two conditions:

  1. Uncommenting the function B(const B&)
  2. change 'main' to the following

    int main()
    {
            A a;
            B b0;
            B b1 = b0;
            return 0;
    }
    

If I do 1, the code compiles, but from the output it says it's calling the 'non const copy constructor'.

Can anyone tell me what's going on here?

using namespace std;
class B
{
 public:
 int k;
  B()
  { 
   cout<<"B()"<<endl; 
  }
  B(int k) 
  { 
   cout<<"B(int)"<<endl;
  this->k = k;
  }
  /*B(const B& rhs) { 
  cout<<"Copy constructor"<<endl;
  k = rhs.k;
  }*/
  B(B& rhs) 
  { 
  cout<<"non const Copy constructor"<<endl;
  k = rhs.k;
  }
  B operator=(B& rhs)
  {
  cout<<"assign operator"<<endl;
  k = rhs.k;
  return *this;
  } 
};

class A
{
 public:
  B get_a(void)
  {
   B* a = new B(10);
   return *a;
  }  
};

int main()
{
 A a;
 B b0 = a.get_a();  // was a.just();
 B b1 = b0 ;
 return 0;
}
+3  A: 

Your get_a() function returns an object B, not a reference (but leaks the newly-created B object). Also, for assignments to work as you're doing, you need to make sure your assignment operator and copy constructors are both taking const B& arguments — then B b1 = b0 will work in this case. This works:

class B
{
public:
  int k;
  B() { cout<<"B()"<<endl; }
  B(int k) { 
    cout<<"B(int)"<<endl;
    this->k = k;
  }
  B(const B& rhs) { 
    cout<<"non const Copy constructor"<<endl;
    k = rhs.k;
  }
  B operator=(const B& rhs) {
    cout<<"assign operator"<<endl;
    k = rhs.k;
    return *this;
  }
};

class A {
public:
  B* get_a(void) {
    B* a = new B(10);
    return a;
  }
  B get_a2(void) {
    B a(10);
    return a;
  }
};

int main() {
  A a;
  B b0 = *a.get_a(); // bad: result from get_a() never freed!
  B b1 = a.get_a2(); // this works too
  return 0;
}
cce
that's fine. I was not really bothered about that. I was just wondering why it won't compile
Roshan
yes, the moment you make the reference in copy constructor a 'const' it compiles, I've noticed that before, my question is why ?
Roshan
ok I fixed up your example there a bit as described.
cce
cce
I don't think anyone answered the "why?" so I'll give it a shot: rvalues (a.k.a. temporaries, e.g. values returned by functions) cannot bind to non-const references, so in order for a function/constructor to be able to bind an rvalue as a parameter with a reference type, it must be a const reference.
GRB
@GRB, thanks for the explanation. If I uncomment the copy constructor which takes a constant reference, the code compiles. BUT, when you run the program, it's using the copy constructor which uses the non-const reference. I am using gcc-4.3.
Roshan
That's a fantastic question, I have no idea why that's happening (happens both with my gcc and msvc). Hopefully someone else smarter than me has an explanation, however in the meantime I'll see if I can do some research
GRB
See my answer for the results of that research :)
GRB
12.2 [class.temporary]: "Even when the creation of the temporary is avoided, all the semantic restrictions must be respected as if the temporary object was created." So a const copy constructor for use by the _copy-initialization_ from the theoretical temporary must exist even if it isn't used.
Charles Bailey
I think you can find an analogue with std::auto_ptr. It too takes a non-const reference in copy constructor, and uses some proxy object voodoo, so you'd be able to return auto_ptrs from functions. But in practice, give your classes normal semantics (no modifying source of copy).
UncleBens
+7  A: 

I've done some extra reading into this, and as I suspected all along, the reason why this occurs is due to return value optimization. As the Wikipedia article explains, RVO is the allowed mechanism by which compilers are allowed to eliminate temporary objects in the process of assigning them or copying them into permanent variables. Additionally, RVO is one of the few features (if not the only) which are allowed to violate the as-if rule, whereby compilers are only allowed to make optimizations only if they have the same observable behaviours as if the optimization were never made in the first place -- an exemption which is key in explaining the behaviour here (I admittedly only learned of that exemption as I was researching this question, which is why I was also confused initially).

In your case, GCC is smart enough to avoid one of the two copies. To boil your code down to a simpler example

B returnB()
{
    B a;
    B* b = &a;
    return *b;
}

int main()
{
    B c = returnB();
    return 0;
}

If one follows the standard and does not perform RVO, two copies are made in the process of making c -- the copy of *b into returnB's return value, and the copy of the return value into c itself. In your case, GCC omits the first copy and instead makes only one copy, from *b directly into c. That also explains why B(B&) is called instead of B(const B&) -- since *b (a.k.a. a) is not a temporary value, the compiler doesn't need to use B(const B&) anymore and instead chooses the simpler B(B&) call instead when constructing c (a non-const overload is always automatically preferred over a const overload if the choice exists).

So why does the compiler still give an error if B(const B&) isn't there? That's because your code's syntax must be correct before optimizations (like RVO) can be made. In the above example, returnB() is returning a temporary (according to the C++ syntax rules), so the compiler must see a B(const B&) copy constructor. However, once your code is confirmed to be grammatically correct by the compiler, it then can make the optimization such that B(const B&) is never used anyway.

EDIT: Hat tip to Charles Bailey who found the following in the C++ standard

12.2 [class.temporary]: "Even when the creation of the temporary is avoided, all the semantic restrictions must be respected as if the temporary object was created."

which just reinforces and confirms the need for a copy constructor taking a reference to const when temporaries are to be copied for construction (irrespective of whether or not the constructor is actually used)

GRB
Nice explanation of RVO.FWIW, here's another explanation of RVO that I found useful:http://parashift.com/c++-faq-lite/ctors.html#faq-10.9
bbg
Thanks for the link. In fact, those interested in learning more about RVO should probably read articles like that since my answer is not really intended to explain RVO generally, but more only in this particular context.
GRB
thanks. this helps
Roshan
Good answer, but your usage of “RVO” is highly unorthodox, and the Wikipedia article on the subject is plain wrong. What you’re describing is *named* return value optimization, “NRVO” for short. In fact, the Wikipedia article contains a specific case where only *NRVO* fails. Strictly speaking, there’s probably no such thing as RVO – although it could be defined as the simple fact that return values are transformed to out arguments. See http://msdn.microsoft.com/en-us/library/ms364057%28VS.80%29.aspx
Konrad Rudolph
Meh … the Wikipedia article correctly cites Meyers (just checked), I guess that counts as authoritative. Once again, computer science demonstrates its complete inability to agree on well-defined terms.
Konrad Rudolph
Excellent answer!
Martin B
The copy that's created in the function in the question (that may be eliminated by NRVO) would work with either form of copy constructor. It's actually the temporary that's made for the return value outside of the called function that is more important in this case. This temporary may also be eliminated, but eliminating this temporary is independent of whether the NRVO occurs inside the function.
Charles Bailey
+1  A: 

The line B b1 = b0; is the culprit. This line requires calling a copy constructor. You could fix the code by writing B b1(b0) instead, or by defining a copy constructor, which takes a const B&, but not a B&.

Max Lybbert
`B b1 = b0;` is an initialization, not an assignment so it *always* uses a constructor. The presence or absence of a `B::operator=` is irrelevant to the semantics of this line of code.
Charles Bailey
Thanks for the correction. I've edited the answer.
Max Lybbert
I've removed my -1, but there is one other small point: a copy constructor can't take a `B` by value, it has to take a reference of some qualification. If it took a `B` by value calling a copy constructor would be infinitely recursive.
Charles Bailey
Thanks again. I've corrected things.
Max Lybbert
+1  A: 

Short explanation: functions that return by value create a temporary object which is treated as constant and therefore cannot be passed to functions accepting by reference, it can only be passed to functions accepting const reference. If you really allocate an object in get_a(), you should really be returning a pointer (so that you remember to delete it, hopefully) or in the worst case - a reference. If you really want to return a copy - create the object on the stack.

Long explanation: To understand why your code doesn't compile if there is only "non-const copy constructor"1, you need to be familiar with the terms lvalue and rvalue. They originally meant that rvalues can only stand on the right side of operator = (assignment) while lvalues can stand also on the left side. Example:

T a, b;
const T ac;
a = b; // a can appear on the left of = 
b = a; // so can b => a and b are lvalues in this context
ac = a; // however, ac is const so assignment doesn't make sense<sup>2</sup>, ac is a rvalue

When the compiler is performing overload resolution (finding which overload of a function/method best match the provided arguments) it will allow lvalues to match parameters passed by value3, reference and const reference types. However, it will match rvalues only against value3 and const reference parameters. And that's because in some sense, since rvalues cannot be put on the left side of operator =, they have read-only semantic, and when it shouldn't be allowed to modify them. And when you accept a parameter through non-const reference, it's implied that you'll somehow change this parameter.

The last piece of the puzzle: temporary objects are rvalues. Function returning by value creates a temporary object with very limited lifespan. Because of its limited lifespan it's considered const, and is therefore a rvalue. And this rvalue doesn't match functions with parameters by non-const reference. Examples:

void f_cref(const A& a) { std::cout << "const ref" << std::endl; }
void f_ref(A& a) { std::cout << "non-const ref" << std::endl; }

A geta() { return A(); }

A a;
const A ac;
f_ref(a); // ok, a is a lvalue
f_ref(ac); // error, passing const to non-const - rvalue as lvalue - it's easy to spot here
f_cref(a); // ok, you can always pass non-const to const (lvalues to rvalues)
f_ref(geta()); // error, passing temporary and therefore const object as reference
f_cref(geta()); // ok, temporary as const reference

Now you have all the information to figure out why your code doesn't compile. Copy constructor are like regular functions.

I have oversimplified things a bit, so better, more complete and correct explanation can be found at this excellent Visual C++ Studio Team blog post about rvalue references, which also addresses the new C++ 0x feature "rvalue references"

1 - there's no such thing as non-const copy constructor. The copy constructor accepts const reference, period.

2 - you can probably put const object on the left of = if it has its operator = declared const. But that would be terrible, terrible, nonsensical thing to do.

3 - actually, you wouldn't be able to pass const A by value if A doesn't have a copy constructor - one that accepts const A& that is.

sbk
rvalue/lvalue and const/non-const are orthogonal. When you say that rvalues are considered const, that's not strictly true. Consider the function `A tmp()`. You can call a non-const function directly on the return value: `tmp().non_const_method()`. You can't bind the return value to a non-const reference as it is not an lvalue, not for any reason to do with its constness (it isn't const).
Charles Bailey