Search code examples
c++pointerscontainersconst-correctness

Const-correct pointer access to items in a container


The Problem

I have a class holding a pointer to a container. The container offers access to subsets of its items. This is realized by a method with const and non-const version returning a vector of const/non-const pointers. The following code in a minimal (not fully functional) excerpt from a more complex setup:

#include <vector>

struct Entity
{ int dummy; };

template <typename Value>
struct MyContainer
{
    typedef Value* pointer;
    typedef Value const* const_pointer;

    std::vector<pointer> pointers() {
        std::vector<pointer> ret;
        return ret;
    }
    std::vector<const_pointer> pointers() const {
        std::vector<const_pointer> ret;
        return ret;
    }
};

struct MyType
{
    typedef MyContainer<Entity> ContainerType;
    ContainerType* m_data;   // I cannot make this a non-pointer attribute

    std::vector<Entity*> pointers() {
        std::vector<Entity*> ret = m_data->pointers();
        return ret;
    }
    std::vector<Entity const*> pointers() const {
        std::vector<Entity const*> ret = m_data->pointers();   // error!
        return ret;
    }
//  std::vector<Entity*> pointers() const {
//      std::vector<Entity*> ret = m_data->pointers();
//      return ret;
//  }
};

int main() {
    MyType obj;
    MyType const& constobj = obj;
    std::vector<Entity*> pointers = obj.pointers();
    std::vector<Entity const*> constpointers = constobj.pointers();
//  std::vector<Entity*> constpointers = constobj.pointers();
}

Clang 3.8.0 (also 3.5.2) reports an error:

error: no viable conversion from 'vector<pointer>' to 'vector<const Entity *>'
            std::vector<Entity const*> ret = m_data->pointers();
                                       ^     ~~~~~~~~~~~~~~~~~~

The commected code compiles fine, however, my intent is to give const access to items obtained through the const access path (via constobj), hence the const method should return pointers of type Entity const*. Changing the type of m_data from ContainerType* to ContainerType solves the problem, but that's not an option in my original code.

Questions:

  1. It seems that MyType::pointers() const calls the non-const method MyContainer::pointers() through its attribute m_data, which is of type MyContainer* const (at least I think so). I apparently need type MyContainer const*. Is there an elegant solution to achieve this?
  2. The not-so-elegant solution is to cast the pointer to a const pointer: std::vector<Entity const*> ret = static_cast<ContainerType const*>(m_data)->pointers(); Is this considered a sensible solution?
  3. More generally: is my design flawed, or am I just missing a detail?

Disclaimer: I hope this is not a duplicate, it seems that someone else must have had the same problem before. Anyway, I did not manage to resolve this from reading related questions on SO. (The question Const correctness causing problems with containers for pointers? is different, it is about a container of pointers. The issue seems to be related, but the answers did not help me to solve my present problem.)


Solution

  • When you write

    m_data->pointers()
    

    you are calling to the same function (the non-const one) because m_data is defined as:

    ContainerType* m_data;
    

    that is, a pointer to a non-const objet.

    When you use constobj the compiler considers m_data as a const member (you can not change constobj), but in this object, the pointer still points to a non-const object.

    I do not know what your application is, but you could consider to add a new function as "cpointers" to return const pointers even if the object is not const.

    EDIT: I mean, something like this:

    struct MyContainer
    {
        typedef Value* pointer;
        typedef Value const* const_pointer;
    
        std::vector<pointer> pointers() {
            std::vector<pointer> ret;
            return ret;
        }
        std::vector<const_pointer> pointers() const {
            std::vector<const_pointer> ret;
            return ret;
        }
        std::vector<const_pointer> cpointers() const {
            std::vector<const_pointer> ret;
            return ret;
        }
    };
    
    struct MyType
    {
    
        typedef MyContainer<Entity> ContainerType;
        ContainerType* m_data;   // I cannot make this a non-pointer attribute
    
        std::vector<Entity*> pointers() {
            std::vector<Entity*> ret = m_data->pointers();
            return ret;
        }
        std::vector<Entity const*> pointers() const {
            std::vector<Entity const*> ret = m_data->cpointers();   // error!
            return ret;
        }
    };