neuro
neuro

Reputation: 15180

Immutable container with mutable content

The story begins with something I thought pretty simple :

I need to design a class that will use some STL containers. I need to give users of the class access to an immutable version of those containers. I do not want users to be able to change the container (they can not push_back() on a list for instance), but I want users to be able to change the contained objects (get an element with back() and modify it) :

class Foo
{
    public:

    // [...]

    ImmutableListWithMutableElementsType getImmutableListWithMutableElements();

    // [...]
};

// [...]

myList = foo.getImmutableListWithMutableElements();
myElement = myList.back();
myElement.change(42); // OK

// [...]

// myList.push_back(myOtherElement); // Not possible

At first glance, it seems that a const container will do. But of course, you can only use a const iterator on a const container and you can not change the content.

At second glance, things like specialized container or iterator come to mind. I will probably end up with that.

Then, my thought is "Someone must have done that already !" or "An elegant, generic solution must exist !" and I'm here asking my first question on SO :

How do you design / transform a standard container into an immutable container with mutable content ?

I'm working on it but I feel like someone will just say "Hey, I do that every time, it's easy, look !", so I ask...

Thank you for any hints, suggestions or wonderful generic ways to do that :)


EDIT:

After some experiments, I ended up with standard containers that handle some specifically decorated smart pointers. It is close to Nikolai answer.

The idea of an immutable container of mutable elements is not a killing concept, see the interesting notes in Oli answer.

The idea of a specific iterator is right of course, but it seems not practical as I need to adapt to any sort of container.

Thanks to you all for your help.

Upvotes: 10

Views: 2732

Answers (5)

pepcots
pepcots

Reputation: 11

After some time, now it is possible in a fashioned way by using views:

const auto& readOnlyList() const { return _list;}
auto readOnlyList() { return _list | std::views::transform([](auto& x)->T& { return x; }); }

Upvotes: 1

vz0
vz0

Reputation: 32923

You need a custom data structure iterator, a wrapper around your private list.

template<typename T>
class inmutable_list_it {
public:
    inmutable_list_it(std::list<T>* real_list) : real_list_(real_list) {}

    T first() { return *(real_list_->begin()); }

    // Reset Iteration
    void reset() { it_ = real_list_->begin(); }

    // Returns current item 
    T current() { return *it_; } 

    // Returns true if the iterator has a next element.
    bool hasNext(); 

private:
    std::list<T>* real_list_;
    std::list<T>::iterator it_;
};

Upvotes: 3

Simon Richter
Simon Richter

Reputation: 29588

The painful solution:

/* YOU HAVE NOT SEEN THIS */
struct mutable_int {
    mutable_int(int v = 0) : v(v) { }
    operator int(void) const { return v; }
    mutable_int const &operator=(int nv) const { v = nv; return *this; }

    mutable int v;
};

Excuse me while I have to punish myself to atone for my sins.

Upvotes: 1

Nikolai Fetissov
Nikolai Fetissov

Reputation: 84169

The simplest option would probably be a standard STL container of pointers, since const-ness is not propagated to the actual objects. One problem with this is that STL does not clean up any heap memory that you allocated. For that take a look at Boost Pointer Container Library or smart pointers.

Upvotes: 5

Oliver Charlesworth
Oliver Charlesworth

Reputation: 272517

Rather than providing the user with the entire container, could you just provide them non-const iterators to beginning and end? That's the STL way.

Upvotes: 5

Related Questions