Karu
Karu

Reputation: 4627

Why does std::vector transfer its constness to the contained objects?

A const int * and an int *const are very different. Similarly with const std::auto_ptr<int> vs. std::auto_ptr<const int>. However, there appears to be no such distinction with const std::vector<int> vs. std::vector<const int> (actually I'm not sure the second is even allowed). Why is this?

Sometimes I have a function which I want to pass a reference to a vector. The function shouldn't modify the vector itself (eg. no push_back()), but it wants to modify each of the contained values (say, increment them). Similarly, I might want a function to only change the vector structure but not modify any of its existing contents (though this would be odd). This kind of thing is possible with std::auto_ptr (for example), but because std::vector::front() (for example) is defined as

const T &front() const;
T &front();

rather than just

T &front() const;

There's no way to express this.

Examples of what I want to do:

//create a (non-modifiable) auto_ptr containing a (modifiable) int
const std::auto_ptr<int> a(new int(3));

//this works and makes sense - changing the value pointed to, not the pointer itself
*a = 4;
//this is an error, as it should be
a.reset();


//create a (non-modifiable) vector containing a (modifiable) int
const std::vector<int> v(1, 3);

//this makes sense to me but doesn't work - trying to change the value in the vector, not the vector itself
v.front() = 4;
//this is an error, as it should be
v.clear();

Upvotes: 4

Views: 964

Answers (4)

Happy Green Kid Naps
Happy Green Kid Naps

Reputation: 1683

  • You are correct, it is not possible to have a vector of const int primarily because the elements will not assignable (requirements for the type of the element contained in the vector).
  • If you want a function that only modifies the elements of a vector but not add elements to the vector itself, this is primarily what STL does for you -- have functions that are agnostic about which container a sequence of elements is contained in. The function simply takes a pair of iterators and does its thing for that sequence, completely oblivious to the fact that they are contained in a vector.
  • Look up "insert iterators" for getting to know about how to insert something into a container without needing to know what the elements are. E.g., back_inserter takes a container and all that it cares for is to know that the container has a member function called "push_back".

Upvotes: 1

Mankarse
Mankarse

Reputation: 40633

The difference is that pointers to int do not own the ints that they point to, whereas a vector<int> does own the contained ints. A vector<int> can be conceptualised as a struct with int members, where the number of members just happens to be variable.

If you want to create a function that can modify the values contained in the vector but not the vector itself then you should design the function to accept iterator arguments.

Example:

void setAllToOne(std::vector<int>::iterator begin, std::vector<int>::iterator end)
{
    std::for_each(begin, end, [](int& elem) { elem = 1; });
}

If you can afford to put the desired functionality in a header, then it can be made generic as:

template<typename OutputIterator>
void setAllToOne(OutputIterator begin, OutputIterator end)
{
    typedef typename iterator_traits<OutputIterator>::reference ref;
    std::for_each(begin, end, [](ref elem) { elem = 1; });
}

Upvotes: 4

Nicol Bolas
Nicol Bolas

Reputation: 473976

One big problem syntactically with what you suggest is this: a std::vector<const T> is not the same type as a std::vector<T>. Therefore, you could not pass a vector<T> to a function that expects a vector<const T> without some kind of conversion. Not a simple cast, but the creation of a new vector<const T>. And that new one could not simply share data with the old; it would have to either copy or move the data from the old one to the new one.

You can get away with this with std::shared_ptr, but that's because those are shared pointers. You can have two objects that reference the same pointer, so the conversion from a std::shared_ptr<T> to shared_ptr<const T> doesn't hurt (beyond bumping the reference count). There is no such thing as a shared_vector.

std::unique_ptr works too because they can only be moved from, not copied. Therefore, only one of them will ever have the pointer.

So what you're asking for is simply not possible.

Upvotes: 1

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385274

It's a design decision.

If you have a const container, it usually stands to reason that you don't want anybody to modify the elements that it contains, which are an intrinsic part of it. That the container completely "owns" these elements "solidifies the bond", if you will.

This is in contrast to the historic, more lower-level "container" implementations (i.e. raw arrays) which are more hands-off. As you quite rightly say, there is a big difference between int const* and int * const. But standard containers simply choose to pass the constness on.

Upvotes: 7

Related Questions