10

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.

5 Answers 5

5

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Yes, but to get an immutable container, I have to return a const container, and then I will get const elements from it, no ? You suggest to use const_cast ? Of course it is a possibility, but it will be a pain to use ... or do i missed something ?
The pointers in the const container will be const, but not the objects they point to.
yes you are right of course. The only thing is that it is not as generic as I thought it could be. As a part of a framework, I'd like to give the possibility to framework developers to use any container type. But using pointer container or smart pointer container should do the job ...
thank you again, as I iimplemented something close to what you have proposed, you are the accepted ;)
5

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.

10 Comments

This works, depending on one's definition of "modifying the container." Does std::remove count as modifying the container? (I'd think so, but perhaps the OP is okay with that.)
yes, but as James said, with a standard non const iterator, the user can modify the container. In any case I have to specialize the container or the iterator. I try to imagine some solution that can "decorate" my container/iterator to have to write less code :) But you are right in saying that it is STL way. A specialized iterator will probably do the job.
@James, @neuro: Would it be fair to say that std::remove etc. don't actually modify the container, per se. They just swap the contents of various elements, which is something the user could do even if std::remove weren't permissible?
I'd argue that algorithms that move elements do modify the container, especially algorithms like std::remove: it doesn't just shuffle elements around; it removes elements from the first part of a sequence and then leaves the contents of the second part of the sequence unspecified.
@James: The size() of the container doesn't actually change, though. If the pseudocode at e.g. cplusplus.com/reference/algorithm/remove is to be believed, it's just trivial assignment operations that the user could do with the hypothetical immutable container of mutable elements.
|
3

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_;
};

2 Comments

Yes, I was thinking of something like that. What bothers me is that I need to code a specialized version for any container type I want to manage ... I will try a generic version of what you propose, if my poor template skills fit the challenge :) Thank you for your help !
thanks for your ideas, it reminds me of some java iterator by the way :)
1

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.

3 Comments

Err, that's probably too smart for me :) Thanks for the effort anyway ;)
Hi, I'm from the future and I'm trying to learn C++. Can anyone please explain what is going on here...? :|
This is a class that wraps an int, but declares the wrapped object mutable so it is unaffected by the const keyword -- i.e. if you have mutable_int const i;, then i.v is non-const. The constructor, assignment operator and cast operator make this behave more like an int.
1

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; }); }

2 Comments

I haven't think about this for a long time :) I'll think about your solution if I have time. Thanks for it!
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.