Why can I call member's non-const function inside const member function?

123 views Asked by At

When I compile below code using VisualStudio 2015, I get C2662 compiler error only on GetValueUsingObject() member function.

Code:

class Member
{
public:
    int GetValue() { _value = 1; return _value; }

private:
    int _value;
};

class Test
{
public:
    Test() : _pointer(new Member()) {}
    int GetValueUsingPointer() const { return _pointer->GetValue(); } // Okay
    int GetValueUsingObject() const { return _object.GetValue(); }    // C2662 Error

private:
    Member* _pointer;
    Member _object;
};

Compile Error Message:

C2662 'int Member::GetValue(void)' : cannot convert 'this' pointer from 'const Member' to 'Member &'

I think that both GetValueUsingPointer() and GetValueUsingObject() function violates const-ness. But, GetValueUsingPointer() has no compiling issue.

Why is it Okay?

3

There are 3 answers

0
463035818_is_not_an_ai On BEST ANSWER

You aren't calling the members function. You use a member pointer (that is const), dereference it, and call a member function of the object the pointer points to. And that object is not const.

If pointers confuse you, consider an example where you have a member that is index into a global array:

std::array<int,42> memory;

struct foo {
    size_t index = 23;
    void bar() const {
        memory[index] = 0;  // does not modify index
    }
};

A const foo has a constant index, but you can use that index to access the index-th element in memory and modify it:

You can imagine pointers as index into memory. A pointer that is constant does not mean that what is stored in that memory is constant. You can have a non-const pointer to constant object, const pointer to non-constant object, etc. In your constant Test the member _pointer is constant, the object it points to is not.

0
CiaPan On

This:

private:
    Member* _pointer;

declares a pointer to some object of the Member class.

The _pointer is a member of your *this object and as such it is treated as const pointer. However, the declaration of _pointer is

    Member* _pointer;

and not

    const Member* _pointer;

or

    Member const* _pointer;

so the object pointed at is not declared as const.

As a result you have a const pointer to a non-const object. Your expression

    _pointer->GetValue()

implies the pointed object may be modified, but it is allowed, hence no error on the right side of the -> operator.
OTOH, it does not imply any action modifying the const _pointer member, hence no error on the left side, either.

0
alagner On

The member is a pointer, which itself remains unchanged. What changes is an object it points to. Therefore it is valid to modify the pointee. Same stands for references. If const should also mean "pure" is an open question. If for for some reason you'd like to ensure the constness of parent object propagates also to its pointees, you need to propagate the constness to the pointee object, e.g. by wrapping the pointer/reference into the appropriate class:

template<typename T>
struct ConstPropagatingPtr
{
    //open question if that should be explicit or not
    /*explicit*/ConstPropagatingPtr(T* p) : contents(p) {}
    ConstPropagatingPtr(ConstPropagatingPtr&) = default;
    ConstPropagatingPtr& operator=(ConstPropagatingPtr&) = default;
    ConstPropagatingPtr& operator=(T* p) { contents = p; return *this; }
    T& operator*() { return *contents; }
    const T& operator*() const { return *contents; }
    const T* getRaw() const { return contents;}
    T* getRaw() { return contents;}
    const T* operator->() const { return contents;}
    T* operator->() { return contents;}
    explicit operator bool() const { return contents != nullptr; }

private:
    T* contents;
};

//example
struct P
{
    void doStuffNonConst() {}
    void doStuffConst() const {}
};

struct S
{
    S() : pptr(new P) {}
    S(const S&) = delete;
    S& operator=(const S&) = delete;
    ConstPropagatingPtr<P> pptr;

    void constMember() const { pptr->doStuffConst(); }
    void anotherConstMember() const
    { 
        //oops! compilation error!
        //pptr->doStuffNonConst();
    }
};


https://godbolt.org/z/vcz4o455Y

Similar wrappers should be provided for references and smart pointers should those be needed. For the sake of the example I have covered only the most basic case.

You can also have a look at std::experimental::propagate_const.