Search code examples
c++copy-constructorconst-reference

what if C++ class contains both const reference and non-const reference copy constructor?


snippet 1:

#include<iostream>
using namespace std;

class C{
public:
    C(){}
    C(const C& c){
        cout<<"const copy constructor called"<<endl;
    }
};
int main(){
    C c1;
    C c2 = c1;
    return 0;
}

output: const copy constructor called


snippet 2:

#include<iostream>
using namespace std;

class C{
public:
    C(){}
    C(const C& c){
        cout<<"const copy constructor called"<<endl;
    }
    C(C& c){
        cout<<"non-const copy constructor called.\t "<<endl;
    }
};
int main(){
    C c1;
    C c2 = c1;
    return 0;
}

output: non-const copy constructor called


snippet 3:

#include<iostream>
using namespace std;

class C{
public:
    C(){}
    C(const C& c){
        cout<<"const copy constructor called"<<endl;
    }
    C(C c){
        cout<<"non-const copy constructor called.\t "<<endl;
    }
};
int main(){
    C c1;
    C c2 = c1;
    return 0;
}

output: error: copy constructor must pass its first argument by reference


I am so confused about:

  1. for snippet 2, why the non-const copy constructor here is valid? why non-const copy constructor was called, rather than the const one.
  2. for snippet 3, I know that copy constructor must use const reference to avoid infinite recursion. But Here class C has got C(const C& c), C(C c) won't cause infinite recursion, why it still doesn't work?

Solution

  • Snippet 1: One standard copy constructor with const T&. Happy world.

    Snippet 2:

    What you have effectively done is overloaded the copy constructor - one that takes a reference T& and the other that takes a constant reference const T&.

    Please note: Any constructor for a class T that has one mandatory argument of type T & or const T & (it may also have further, defaulted arguments) is a copy constructor.

    So, for the compiler, it all just boils down to finding the Best Fit for overload resolution and it is done as:

    Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if:

    • ....
    • S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

    So writing

    C c1;
    C c2 = c1;
    

    will call the non-const copy constructor since it is a better match, but,

    writing,

    const C c1;
    C c2 = c1;
    

    will call the const copy constructor (you can check) since now the copy constructor with const is the only viable match.

    Snippet 3 is just plain wrong for the compiler.

    C(C c){
            cout<<"non-const copy constructor called.\t "<<endl;
        }
    

    You can't have a method with a signature C(C c). The compiler thinks that you are trying to write a copy constructor and missed writing the & and hence reports the error. Remove it and it works fine.

    @Unless you have a very good reason, never ever use C(C& c) for your copy constructor. Don't skip const because mutating the object from which you are making a copy doesn't make much sense.