Reputation: 4645
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?
Upvotes: 4
Views: 697
Reputation: 311156
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.:) )
Upvotes: 7
Reputation: 96865
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);
Upvotes: 5