4

Update: The suggested duplicate only addresses part of this question. The key to understand what is going on (the fact that a temporary reference is first created) isn't explained there.

This is my first time with implicit conversions, so I wrote this:

class A {};

class B {
public:
    B(A& other) {}
    // Copy constructor
    B(const B& other) {}
};

int main() {
    A foo;
    B bar = foo;
}

This compiles as expected, but if I remove the const, my compiler (gcc version 4.8.4) yields at the assignment, with an error message I'm not able to make sense of:

test.cc: In function ‘int main()’:
test.cc:12:13: error: no matching function for call to ‘B::B(B)’
     B bar = foo;
             ^
test.cc:12:13: note: candidates are:
test.cc:7:5: note: B::B(B&)
     B(B& other) {}
     ^
test.cc:7:5: note:   no known conversion for argument 1 from ‘B’ to ‘B&’
test.cc:5:5: note: B::B(A&)
     B(A& other) {}
     ^
test.cc:5:5: note:   no known conversion for argument 1 from ‘B’ to ‘A&’

Is that valid C++ code? Why does it say no matching function for call to ‘B::B(B)’ when I'm trying to assign an A to begin with?

Alba Mendez
  • 4,432
  • 1
  • 39
  • 55
  • 1
    Possible duplicate of [Why C++ copy constructor must use const object?](http://stackoverflow.com/questions/16956693/why-c-copy-constructor-must-use-const-object) – m.s. Oct 30 '15 at 15:54
  • This must have something to do with the move operator, because `B bar( foo );`works with no complain if `const` is removed. – jpo38 Oct 30 '15 at 15:56
  • Interesting that removing `const` works under gcc 4.1.2. – dbush Oct 30 '15 at 15:56
  • @dbush not sure that compiler must provide diagnostics when copy ctor call is eliminated by optimization, but language requires it's accessibility – Slava Oct 30 '15 at 15:58

2 Answers2

7

This declaration

B bar = foo;

works the following way:

At first the compiler creates a temporary object using constructor:

B(A& other) {}

then it tries to use this temporary object in the copy constructor:

B(B& other) {}

But it may not bind a temporary object with non-constant reference and it issues an error.

When you are using the equal sign then there is used so-called copy-initialization.

If you wrote

B bar( foo );

then here would used so-called direct initialization that is the copy constructor is not called. In this case this code will compile.

Take into account that the copy/move constructor may be bypassed and the temporary object can be built directly in the destination object. This is called copy elision. Nevertheless the all rules shall be kept as if the copy/move constructor is called explicitly.

For example if you add output statements for the constructors of class B

class A {};

class B {
public:
    B(A& other) { std::cout << "B::B( A & )" << std::endl; }
    // Copy constructor
    B(const B& other) { std::cout << "B::B( const B & )" << std::endl; }
};

int main()
{
    A foo;
    B bar = foo;
}

then you will not see the message

B::B( const B & )

However the copy constructor shall be accessible.

For example if you make it private

class A {};

class B {
public:
    B(A& other) { std::cout << "B::B( A & )" << std::endl; }
    // Copy constructor
private:
    B(const B& other) { std::cout << "B::B( const B & )" << std::endl; }
};

int main()
{
    A foo;
    B bar = foo;
}

the program won't compile (only if it is not the MS VC++ compiler.:) )

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
5

B bar = foo is called copy-initialization: When the type of the right hand side doesn't match the type of the left hand side (in this case, a B vs A) the compiler creates a temporary initialized from the right hand side and then copies it using the copy-constructor. This is effectively your code:

B bar = B(foo);

Since you've removed const from your copy-constructor, you now get an error because you're trying to bind an rvalue to an lvalue reference. Rvalues can bind to const lvalue references as you've already seen.

This can be resolved by using direct-initialization. Now no copy is being created:

B bar(foo);
David G
  • 94,763
  • 41
  • 167
  • 253
  • Sidenote: it was [this tutorial in cplusplus.com](http://www.cplusplus.com/doc/tutorial/typecasting) (see example in second section) that gave me the idea that the conversion constructor was called directly. – Alba Mendez Oct 30 '15 at 16:16
  • 1
    @jmendeth Indeed, cplusplus is not the best tutorial website. I can also see it was mistaken about the "three" member functions that allow conversions. There's only two, a converting constructor (one argument constructor) and a conversion operator. The assignment operator is just another member function and has nothing to do with conversions. – David G Oct 30 '15 at 16:46