peter.slizik
peter.slizik

Reputation: 2076

Overloading typecasting operators: const & non-const

Why do the following two usecases behave differently?

An example for a built-in type:

class Test
{
    operator const char*() {
        return "operator const char*() called";
    }

    operator char*() {
        return const_cast<char*> ("operator char*() called");
        // a really ugly hack for demonstration purposes
    }
};

Test test;
const char *c_ch = test;  // -> operator const char*() called
char *ch = test;          // -> operator char*() called

Okay, everything works as it should. No, let's try it with a user-defined type:

class ColorWrapper
{
    operator const Color() {
        cout << "operator const Color() called" << endl;
        return Color(10, 20, 30);
    }

    operator Color() {
        cout << "operator Color() called" << endl;
        return Color(11, 22, 33);
    }
};

ColorWrapper cw;
const Color c_col = cw;
Color col = cw;

The situation looks identical; but now, GCC starts complaining:

error: conversion from 'ColorWrapper' to 'const Color' is ambiguous
error: conversion from 'ColorWrapper' to 'Color' is ambiguous

Am I missing something? What I'm trying to achieve here is to prevent the user from changing the color directly. If they want to change it, they must do it via the wrapper class.

Upvotes: 3

Views: 2402

Answers (4)

Christian Rau
Christian Rau

Reputation: 45968

The thing is, that your two examples are not equivalent. The first returns two different types from which the compiler can easily choose, a pointer to mutable data and a pointer to constant data. It would be different, was it a pointer vs a constant pointer (which would mean you cannot change where the returned pointer points, in contrast to not changing the pointed to data):

class Test
{
    operator char * const();
    operator char *();
};

which would be equivalent to your second example and introduce the same ambiguity, since the compiler cannot decide which one to choose, as both return the same type, one const-qualified and the other not.

This could be solved by overloading based on the constness of the object:

class ColorWrapper
{
    operator const Color() const;
    operator Color();
};

But this won't buy you anything here, since you return a new object by value anyway, so you can always return the const version, and also the appropriate version would be chosen based on the source object's constness and not the destination obejct's constness (thus call the non-const version in both cases), which doesn't seem what you want.

So you can overload based on the constness of the source object. But you cannot, as you want to now, overload simply based on the constness of the destination object.

This all reduces down to thinking about the difference between an object and the data the object may inderectly refer to, which in practice presents itself as the difference between a constant pointer and a pointer to constant data.

What I'm trying to achieve here is to prevent the user from changing the color directly

Then there's absolutely no need for any overloading. Just return the const version. The user cannot change the returned color directly, he can only copy it and change the copy.

cw.set(red);    //nope, return color is constant
Color col = cw; //copy returned color into col
col.set(green); //fine, since we're not working on the returned color, but on col

const Color c_col = cw; //use this if you don't want c_col to be modified, 
                        //doesn't have anything to do with returned color, though

Upvotes: 3

Mateusz Pusz
Mateusz Pusz

Reputation: 1393

You observe that problem because you basically have 2 different cases here. I think that the easiest way to present what is happening here is to modify a bit your first example:

class Test
{
    operator char* const() {
        return "operator char* () called";
    }

    operator char*() {
        return const_cast<char*> ("operator char*() called");
        // a really ugly hack for demonstration purposes
    }
};

Now you have exactly the same situation as in the second example. You basically return a copy (of a pointer) and a const copy and that is why they mess up in overload resolution.

Your original first example works fine because those conversion operators convert to pointers of 2 different types (const char and char). I hope that helps to understand the difference here.

BTW, I personally believe that there is no much sense in returning a const copy from a method. It does not change anything to the user and only obfuscates the class interface.

Upvotes: 1

RoundPi
RoundPi

Reputation: 5947

That is because in your second example you were adding const to a object , not pointer(in your first case). And const with object make compile confuse in overload resolution stage, those two are as good as each other(object vs const object) according to c++ standard. compiler won't be try to smart here and choose one over another.

But in pointer vs const pointer (reference works too), compile can choose from those two overload candiate accordingly,which is your first case.

Upvotes: 2

Olaf Dietsche
Olaf Dietsche

Reputation: 74118

If you modify one operator to be const, it will work:

operator const Color() const {
    cout << "operator const Color() const called" << endl;
    return Color(10, 20, 30);
}

Alternatively, you can remove one of the two operators. Since you return the Color by value, there's no real difference between the two operators.

Upvotes: 1

Related Questions