Search code examples
c++oopconstants

Implementing a container for both const and mutable types in C++


Let's say we have a class X that holds a pointer to an object of class Y. X never changes Y in any way, but other objects which might want to change Y can ask X for a pointer to Y. We want class X to be able to hold both const and variable objects. If we write something like this:

class Y;

class X {
public:
  const Y* getY();
private:
  const Y* y;
};

then we could never alter Y when getting it from X, even when the original "Y object" is not const.

An example where this would be useful is a linked list that holds both const and variable objects.

How would one go about implementing this?


Solution

  • You can think about the analogous situation of std::unique_ptr: one that holds a pointer to a mutable Y is std::unique_ptr<Y>, while one that holds a pointer to a const Y is std::unique_ptr<const Y>.

    X can similarly be made into a template:

    class Y;
    
    template <class T>
    class X {
    public:
        T* getY();
    private:
        T* p;
    };
    

    Here, X<Y> can hold a Y*, and X<const Y> can hold a const Y*. You may also want to make X<Y> implicitly convertible to X<const Y> by providing an appropriate constructor, so that any function that has a parameter of type X<const Y> can be called with an argument of type X<Y>:

    template <class T>
    class X {
    public:
        X(const X& other) = default;
    
        X(const X<std::remove_const_t<T>>& other)
        requires (!std::is_const_v<T>)
        : p(other.getY()) {}
    
        T* getY() const;
    private:
        T* p;
    };
    

    If you want to hide the templatedness from the users, you can do so like this:

    namespace detail {
    template <class T>
    class X_impl {
    public:
        X_impl(const X_impl& other) = default;
    
        X_impl(const X_impl<std::remove_const_t<T>>& other)
        requires (!std::is_const_v<T>)
        : p(other.getY()) {}
    
        T* getY() const;
    private:
        T* p;
    };
    }  // namespace detail
    
    using X = X_impl<Y>;
    using CX = X_impl<const Y>;