Linlix
Linlix

Reputation: 387

Why is conversion from const pointer-to-const to const pointer-to-nonconst in an initializer list allowed

I read the question posted on Why does C++ not have a const constructor?

I am still confused why that program can compile. And I tried to offer my opinion on the question, I don't know why it was deleted. So I have to ask the question again.

Here is the program

class Cheater
{
public:
    Cheater(int avalue) :
    value(avalue),
    cheaterPtr(this) //conceptually odd legality in const Cheater ctor
    {}

    Cheater& getCheaterPtr() const {return *cheaterPtr;}
    int value;
private:
    Cheater * cheaterPtr;
};
int main()
{
    const Cheater cheater(7); //Initialize the value to 7

 //   cheater.value                 = 4;    //good, illegal
    cheater.getCheaterPtr().value = 4;    //oops, legal
    return 0;
}

And my confusion is :

const Cheater cheater(7) creates a const object cheater, in its constructor

  Cheater(int avalue) :
    value(avalue),
    cheaterPtr(this) //conceptually odd legality in const Cheater ctor
{}

'this' pointer was used to initialize cheaterPtr.

I think it shouldn't be right. cheater is a const object, whose this pointer should be something like: const Cheater* const this; which means the pointer it self and the object the pointer points to should both const, we can neither change the value of the pointer or modify the object the pointer points to.

but object cheater's cheaterPtr member is something like Cheater* const cheaterPtr. Which means the pointer is const but the object it points to can be nonconst.

As we know, pointer-to-const to pointer-to-nonconst conversion is not allowed:

int i = 0;
const int* ptrToConst = &i;
int * const constPtr = ptrToConst;  //  illegal. invalid conversion from 'const int*' to 'int*'

How can conversion from pointer-to-const to pointer-to-nonconst be allowed in the initializer list? What really happened?

And here is a discription about "constness" in constructors I tried to offer to the original post:

"Unlike other member functions, constructors may not be declared as const. When we create a const object of a class type, the object does not assume its 'constness' until after the constructor completes the object's initialization. Thus, constructors can write to const objects during their construction."

--C++ Primer (5th Edition) P262 7.1.4 Constructors

Upvotes: 2

Views: 896

Answers (2)

WhozCraig
WhozCraig

Reputation: 66194

Your assumptions are incorrect. Taking them one at a time, first code annotation.

class Cheater
{
public:
    Cheater(int avalue) :
        value(avalue),
        cheaterPtr(this) // NOTE: perfectly legal, as *this is non-const
                         // in the construction context.
    {}

    // NOTE: method is viable as const. it makes no modifications
    //  to any members, invokes no non-const member functions, and
    //  makes no attempt to pass *this as a non-const parameter. the
    //  code neither knows, nor cares whether `cheaterPtr`points to
    //  *this or not.
    Cheater& getCheaterPtr() const {return *cheaterPtr;}

    int value;

private:
    Cheater * cheaterPtr; // NOTE: member pointer is non-const.
};


int main()
{
    // NOTE: perfectly ok. we're creating a const Cheater object
    //  which means we cannot fire non-const members or pass it
    //  by reference or address as a non-const parameter to anything.
    const Cheater cheater(7);

    // NOTE: completely lega. Invoking a const-method on a const
    // object. That it returns a non-const reference is irrelevant
    // to the const-ness of the object and member function.    
    cheater.getCheaterPtr().value = 4;

    return 0;
}

You said:

I think it shouldn't be right. cheater is a const object whose this pointer should be something like: const Cheater* const this

cheater is const after construction. It must be non-const during construction. Further, the constructor does not (and cannot) know the caller has indicated the object will be const. All it knows is its time to construct an object, so thats what it does. Furthermore, after construction &cheater is const Cheater *. Making the actual pointer var itself also const is simply non-applicable in this context.

And then...

...object cheater's cheaterPtr member is something like Cheater* const cheaterPtr;

This is actually an incredibly accurate way to describe this. Because cheater is const its members are as well, which means the cheaterPtr member is const; not what it points to. You cannot change the pointer value, but because it is not a pointer-to-const-object you can freely use that pointer to modify what it points to, which in this case happens to be this.

If you wanted both the pointer and its pointed-to-object to be const you whould have declared it as const Cheater *cheaterPtr; in the member list. (and doing that, btw, makes only that mutable action through the getCheaterPointer() invalid. It would have to return a const Cheater* as well, which means of course the assignment would fail.

In short, this code is entirely valid. What you're wanting to see (construction awareness of the callers const-ness) is not part of the language, and indeed it cannot be if you want your constructors to have the latitude to... well, construct.

Upvotes: 1

If constructors were const, they couldn't construct their object - they couldn't write into its data!

The code you cite as "legal:"

cheater.getCheaterPtr().value = 4;    //oops, legal

is not actually legal. While it compiles, its behaviour is undefined, because it modifies a const object through a non-const lvalue. It's exactly the same as this:

const int value = 0;
const int * p = &value;
*const_cast<int*>(p) = 4;

This will also compile, but it's still illegal (has UB).

Upvotes: 3

Related Questions