Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?
like image 534
expoter Avatar asked Feb 22 '16 05:02

expoter


2 Answers

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.

like image 172
Akshay Arora Avatar answered Nov 15 '22 01:11

Akshay Arora


for snippet 2, why the non-const copy constructor here is valid? why non-const copy constructor was called, rather than the const one.

Consider your code for this question, but with the change below with the comment // (*):

int main(){
    const C c1; // (*) <- See change here
    C c2 = c1;
    return 0;
}

This calls the const copy ctor version. It really has nothing to do with the function happening to be a constructor - if a function has two overloads, one taking a reference, and one a const reference, then non-const objects will be called with the first, and const objects with the second.

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?

Consider the following code, and note that there is no invocation going on (the content of main is pretty much erased).

#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(){
// Note that nothing is creating C instances at all.
return 0;
}

This results in the exact same error - the compiler simply refuses to compile a class with this interface, regardless of whether something is attempting to call it or not.

Quoting from an answer to this question "it's forbidden by the standard in §12.8/3:

A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv- qualified) X and either there are no other parameters or else all other parameters have default arguments.

"

like image 24
Ami Tavory Avatar answered Nov 14 '22 23:11

Ami Tavory