29

Please help me understand how exactly the conversion operators in C++ work. I have a simple example here which I am trying to understand, though it is not very clear how the conversion actually happens by the compiler.

class Example{
public:
    Example();
    Example(int val);
    operator unsigned int();
    ~Example(){}
private:
    int itsVal;
};

Example::Example():itsVal(0){}

Example::Example(int val):itsVal(val){}

Example::operator unsigned int (){
    return (itsVal);
}

int main(){
    int theInt = 5;
    Example exObject = theInt; // here 
    Example ctr(5);
    int theInt1 = ctr; // here
    return 0;
}
Ravid Goldenberg
  • 2,119
  • 4
  • 39
  • 59
Zuzu
  • 3,363
  • 5
  • 27
  • 16
  • 17
    @Zuzu: I notice you have `using namespace std;` near the top of the file. I'm guessing that your lecturer or teaching material has it like this. You happen not to need it in this example, but be careful where you use it. **Never** ever use it in a header file - it can cause untold grief if you want to use names yourself that happen to be in the `std` namespace. Consider never using it at all, but if you're like me you might want to use it only in modules (.cpp files) and after all #includes, but before function and method definitions. – quamrana Sep 05 '09 at 16:51

4 Answers4

10

You can walk through that code with a debugger (and/or put a breakpoint on each of your constructors and operators) to see which of your constructors and operators is being invoked by which lines.

Because you didn't define them explicitly, the compiler also created a hidden/default copy constructor and assignment operator for your class. You can define these explicitly (as follows) if you want to use a debugger to see where/when they are being called.

Example::Example(const Example& rhs)
: itsVal(rhs.itsVal)
{}

Example& operator=(const Example& rhs)
{
    if (this != &rhs)
    {
        this->itsVal = rhs.itsVal;
    }
    return *this;
}
ChrisW
  • 54,973
  • 13
  • 116
  • 224
  • Do you know how to turn off RVO? – Mykola Golubyev Sep 05 '09 at 16:00
  • The following two lines are killing me.. Example exObject = theInt; int theInt1 = ctr; Is a copy constructor called in this program and how about the assignment operator. None of them seem to be called. – Zuzu Sep 05 '09 at 16:48
6
int main() {
    int theInt = 5;

    /**
     * Constructor "Example(int val)" in effect at the statement below.
     * Same as "Example exObject(theInt);" or "Example exObject = Example(theInt);"
     */
    Example exObject = theInt; // 1

    Example ctr(5);

    /**
     * "operator unsigned int()" in effect at the statement below.
     * What gets assigned is the value returned by "operator unsigned int()".
     */
    int theInt1 = ctr; // 2

    return 0;
}

At statement 1 the constructor Example(int val) is called. Declare it as explicit Example(int val) and you will get a compile time error i.e. no implicit conversion will then be allowed for this constructor.

All single argument constructors are called implicitly if the assigned value is of their respective argument type. Using the explicit keyword before single argument constructors disables implicit constructor calling and hence implicit conversion.

If the constructor was declared as explicit i.e. explicit Example(int val) then the following would happen for each statement.

Example exObject(theInt); // Compile time error.
Example exObject = theInt; // Compile time error.
Example exObject(Example(theInt)); // Okay!
Example exObject = Example(theInt); // Okay!

Also note that in case of implicit constructor call and hence implicit conversion the assigned value is an rvalue i.e. an un-named object implicitly created using an lvalue (theInt) which tells us that in case of implicit conversion the compiler converts

Example exObject = theInt;

to

Example exObject = Example(theInt);

So (in C++11) don't expect the lvalue constructor to be called seeing that you are using an lvalue i.e. a named value theInt for assignment. What gets called is the rvalue constructor since the assigned value is actually the un-named object created using the lvalue. However, this applies if you have both lvalue and rvalue versions of the constructor.

At statement 2 operator unsigned int() is called. Simply consider it as a normal function call with a weird name and the fact that it can get called automagically when an implicit conversion happens. The value returned by that function is the value assigned in the expression. And since in you implementation the value returned is an int it correctly gets assigned to int theInt1.

To be precise operator unsigned int() overloads () operator which is the cast operator. In your case it's overloaded for int hence whenever an object of Example class is assigned to an int the implicit type casting from Example to int takes place and hence operator unsigned int() gets called. Therefore,

int theInt1 = ctr;

is equivalent to

int theInt1 = (int)ctr;
Mubeen Iqbal
  • 188
  • 3
  • 12
  • 1
    Example exObject(theInt); even if the constructor is explicit, I don't think it should be compile time error. Direct initialization is allowed. – bornfree Dec 14 '15 at 10:26
  • This is odd: `Example exObject(Example(theInt));`. Are you sure it's right? Maybe you intended to write `Example exObject((Example)theInt);` – mathiasfk Jan 04 '17 at 16:16
4
Example exObject = theInt; // implicitly created copy constructor takes place
// object implicitly created from int and then copied
// it is like
Example exObject = Example(theInt);
// so it uses sequence
// Example(int) -> Example(const Example&)
int theInt1 = ctr; // operator int()

If you compiler supports copy constructor optimization and return value optimization you won't notice

Example(const Example&)

execution, but you can declare copy constructor to be private to understand what I am talking about.

Mykola Golubyev
  • 57,943
  • 15
  • 89
  • 102
  • 2
    Example(int) is not a copy constructor. – Cat Plus Plus Sep 05 '09 at 15:42
  • Example(int) is not, but Example example = int is. It uses implicitly created copy constructor, no? – Mykola Golubyev Sep 05 '09 at 15:43
  • 3
    Copy ctor would take [const] Example& as first argument. In this case compiler implicitly calls Example(int), which is a normal ctor with an argument: http://codepad.org/cGZ6YgT2 – Cat Plus Plus Sep 05 '09 at 15:49
  • 2
    I think it is just copy constructor optimization. – Mykola Golubyev Sep 05 '09 at 15:53
  • Yes, initialization via `T x = (U)y` will create a temporary of `T`, initializing it via `T(U)` ctor, and then initialize `x` via copy constructor from that temporary - except that this step can be optimized away (but check for accessible constructor must still be there). – Pavel Minaev Sep 05 '09 at 17:47
3
Example exObject = theInt; // here

This uses implicit conversion of int to Example, effected by the non-explicit constructor which accepts an int.

This also requires the availability of copy constructor for Example, even though the compiler is allowed to omit copying the instance.

int theInt1 = ctr; // here

This uses implicit conversion of Example to unsigned int, provided by the cast operator.

Cast operators are normally avoided, since they tend to lead to confusing code, and you can mark single-argument constructors explicit, to disable implicit conversions to your class type. C++0x should add also the possibility to mark conversion operators explicit (so you'd need a static_cast to invoke them? - my compiler doesn't support them and all web resources seem to be concentrating on explicit conversion to bool).

UncleBens
  • 40,819
  • 6
  • 57
  • 90