c++constantsgettersetter

How to make C++ container class store pointer to a const, but get as a non-const?


I have the some code similar to the following. This is obviously a stripped-down version.

struct Foo {
    int bar;
    Foo() : bar(0) {}
};

class Container {
    Foo* foo;
    
    public:
        void put(const Foo* in_foo) {
            foo = in_foo;
        }
        
        Foo* get() {
            return foo;
        }
};

int main()
{
    Foo foo;
    Container c;
    
    c.put(&foo);
    
    Foo* pFoo = c.get();
    pFoo->bar = 3;
}

Running this, I get an error message:

main.cpp: In member function ‘void Container::put(const Foo*)’: main.cpp:20:19: error: invalid conversion from ‘const Foo*’ to ‘Foo*’ [-fpermissive]

See https://onlinegdb.com/2SrlyGdQY .

I want to "promise" that I'm not going to mess with Foo when putting into my container, but not force Foo to be a const when the user gets it back out. What's the best way to handle this?


Solution

  • I want to "promise" that I'm not going to mess with Foo when putting into my container, but not force Foo to be a const when the user gets it back out. What's the best way to handle this?

    OK. First consider what std::vector would do. std::vector<Foo>::push_back(value) stores a copy of value in the container. More formally:

    Appends the given element value to the end of the container. The new element is initialized as a copy of value.

    Storing pointers in std::vector does not change much. Neither push_back nor any other method dereferences one of the pointers in a std::vector<Foo*> or std::vector<const Foo*>. The "promise" to not do weird / silly / unexpected stuff is implicit. If push_back would modify elements or use a pointer to modify other stuff it would be broken.


    Next, consider what issues would arise if you could turn a const Foo* easily into a Foo*:

    class BrokenContainer {
        Foo* foo;        
        public:
            void put(const Foo* in_foo) { magic(foo,in_foo); }
            Foo* get() { return foo; }
    };
    
    int main() {
        const Foo foo;        // const !
        BrokenContainer c;
        c.put(&foo);          // ok
        Foo* pFoo = c.get();  // ok?
        pFoo->bar = 3;        // no!
    }
    

    The (implicit) promise a Foo* get() makes to the user is: "If you passed me a valid pointer to put then the pointer returned from get can be used to modify the pointee". Hence any magic would surprisingly break user code.


    You may have heard about const_cast. It cannot solve the issue illustrated above, it's the tool to create the mess.

    Really the only case where you would cast const away is when you just added the const (for more details see How do I remove code duplication between similar const and non-const member functions?). Any attempt to remove const across interfaces breaks const correctness and will end in tears (there is just no way to tell from a const Foo* whether it points to a constant or non-constant Foo).


    You want a class that stores a pointer to a Foo. A user of the class can retrieve that pointer to modify the Foo. The way to express that is:

    class Container {
        Foo* foo;
        public:
            void put(Foo* in_foo) { foo = in_foo; }          
            Foo* get() { return foo; }
    };
    

    A "Container" is not expected to mess with what it contains. If the user wants to retrieve a pointer to a modifiable Foo they need to pass one.