61

My comments on this answer got me thinking about the issues of constness and sorting. I played around a bit and reduced my issues to the fact that this code:

#include <vector>

int main() {
    std::vector <const int> v;  
}

will not compile - you can't create a vector of const ints. Obviously, I should have known this (and intellectually I did), but I've never needed to create such a thing before. However, it seems like a useful construct to me, and I wonder if there is any way round this problem - I want to add things to a vector (or whatever), but they should not be changed once added.

There's probably some embarrassingly simple solution to this, but it's something I'd never considered before. I probably should not have mentioned sorting (I may ask another question about that, see this for the difficulties of asking questions). My real base use case is something like this:

vector <const int> v;     // ok (i.e. I want it to be OK)
v.push_back( 42 );        // ok
int n = v[0];             // ok
v[0] = 1;                 // not allowed
Community
  • 1
  • 1
  • You can probably do this with reference types, correct? Say `std::vecor`. Then the objects themselves don't change but you can enforce whatever ordering you want. – mrmcgreg May 03 '10 at 16:06
  • @user285915 Obviously, but that's not what I'm asking about - the pointers could be changed after adding. –  May 03 '10 at 16:07
  • 20
    For those wondering the reason why the above can't be guaranteed to work is that elements in a container must be copy-constructible and copy-assignable. The latter doesn't work for const objects. (Note, the above will work in VS because it doesn't take use the copy-assignable requirement in that case, however GCC does and it will fail. Both implementations are correct, though.) – GManNickG May 03 '10 at 16:11
  • related: http://stackoverflow.com/questions/2102244/vector-and-const – swegi May 03 '10 at 16:14
  • 2
    I'm not sure I really understand the question. A type used in a vector must be _Assignable_ so even if you use a proxy object to attempt to add constness to a wrapped object at some level, the reference returned by `op[]`, `at` or a derefenced iterator will be assignable from another externally created proxy object so however the proxy is implemented has to be easily defeatable. Am I missing something? – CB Bailey May 03 '10 at 16:17
  • 1
    @GMan Does VS really allow this - I would have thought it broken if it did. What's your basis for saying both are correct? –  May 03 '10 at 16:17
  • @Charles My point is that I had embarrassingly (because it is obvious) not realised that I could not have real const objects in a container, and this seemed like a deficiency in the language to me. I was kind of hoping someone would come up with "Well, in C++0x you can..." –  May 03 '10 at 16:20
  • @Neil: So how would you think you'd go about setting the value of a cell in the vector? Or reordering it without writing to any of the cells? (I'm trying to understand why you believe this could be made to work...) – Donal Fellows May 03 '10 at 16:23
  • 1
    @Donal Fellows: Even in C++03 you can perform in-place destruction and then placement copy-construction with `const` types so it's technically feasible to build a container of `const` types. – CB Bailey May 03 '10 at 16:32
  • 4
    @Neil: Indeed, VS (Dinkumware, really) will use the copy-constructor when adding elements, and while copying them. Contrarily, GCC uses assignment to assign the values. The reason I said both are correct is implementations can do whichever they feel like. However, one might argue VS allowing this is an "extension" because const types don't satisfy container requirements, and the fact it happens to work is merely an implementation detail. – GManNickG May 03 '10 at 16:35
  • @GMan See avakar's answer below. I must admit I'm not clear if a vector implementation is required to test for both abilities. –  May 03 '10 at 17:25
  • 1
    +1 For not shying away from potential embarassment – Josh Stodola May 03 '10 at 17:30
  • @Neil: As of C++03, because the requirements are container-wide an implementation is free to use both copy-constructibility and copy-assignment to perform whichever tasks it needs. I do find it surprising gcc feels the need to use assignment, but it's okay to do so. I didn't notice 0x made the requirements much more strict, which I like. I think avakar's answer is all correct. – GManNickG May 03 '10 at 17:43
  • @GMan: I think gcc did so because it might be cheaper to perform assignment than destruction + in-place construction. – Matthieu M. May 03 '10 at 17:50
  • Though I feel the example here contrived, as anyone ever tried to (naively) use `remove_if` on a `map` ? The idiom seems useful, however because of the definition of map value `std::pair` you just can't use of the algorithm that would rearrange the values (`remove_if` being the one I find the most useful here). – Matthieu M. May 03 '10 at 17:56
  • I thought maybe one could work around this using a `boost::ptr_vec` but it suffers from the same problem as the original. Truthfully, I don't see much utility in this, but it should be easily constructed using a simple facade over the desired container. – Nathan Ernst May 03 '10 at 22:41

14 Answers14

22

Well, in C++0x you can...

In C++03, there is a paragraph 23.1[lib.containers.requirements]/3, which says

The type of objects stored in these components must meet the requirements of CopyConstructible types (20.1.3), and the additional requirements of Assignable types.

This is what's currently preventing you from using const int as a type argument to std::vector.

However, in C++0x, this paragraph is missing, instead, T is required to be Destructible and additional requirements on T are specified per-expression, e.g. v = u on std::vector is only valid if T is MoveConstructible and MoveAssignable.

If I interpret those requirements correctly, it should be possible to instantiate std::vector<const int>, you'll just be missing some of its functionality (which I guess is what you wanted). You can fill it by passing a pair of iterators to the constructor. I think emplace_back() should work as well, though I failed to find explicit requirements on T for it.

You still won't be able to sort the vector in-place though.

avakar
  • 32,009
  • 9
  • 68
  • 103
  • 1
    +1 Sounds like it might work - doesn't though with g++ 4.4.1 (latest I have) with `-std=c++0x` –  May 03 '10 at 17:02
  • Accepting this, because it suggests that what I want may be available in the future - I like to live in hope! –  May 09 '10 at 15:11
17

Types that you put in a standard container have to be copyable and assignable. The reason that auto_ptr causes so much trouble is precisely because it doesn't follow normal copy and assignment semantics. Naturally, anything that's const is not going to be assignable. So, you can't stick const anything in a standard container. And if the element isn't const, then you are going to be able to change it.

The closest solution that I believe is possible would be to use an indirection of some kind. So, you could have a pointer to const or you could have an object which holds the value that you want but the value can't be changed within the object (like you'd get with Integer in Java).

Having the element at a particular index be unchangeable goes against how the standard containers work. You might be able to construct your own which work that way, but the standard ones don't. And none which are based on arrays will work regardless unless you can manage to fit their initialization into the {a, b, c} initialization syntax since once an array of const has been created, you can't change it. So, a vector class isn't likely to work with const elements no matter what you do.

Having const in a container without some sort of indirection just doesn't work very well. You're basically asking to make the entire container const - which you could do if you copy to it from an already initialized container, but you can't really have a container - certainly not a standard container - which contains constants without some sort of indirection.

EDIT: If what you're looking to do is to mostly leave a container unchanged but still be able to change it in certain places in the code, then using a const ref in most places and then giving the code that needs to be able to change the container direct access or a non-const ref would make that possible.

So, use const vector<int>& in most places, and then either vector<int>& where you need to change the container, or give that portion of the code direct access to the container. That way, it's mostly unchangeable, but you can change it when you want to.

On the other hand, if you want to be able to pretty much always be able to change what's in the container but not change specific elements, then I'd suggest putting a wrapper class around the container. In the case of vector, wrap it and make the subscript operator return a const ref instead of a non-const ref - either that or a copy. So, assuming that you created a templatized version, your subscript operator would look something like this:

const T& operator[](size_t i) const
{
    return _container[i];
}

That way, you can update the container itself, but you can't change it's individual elements. And as long as you declare all of the functions inline, it shouldn't be much of a performance hit (if any at all) to have the wrapper.

Jonathan M Davis
  • 37,181
  • 17
  • 72
  • 102
  • I've edited my question, hopefully not changing the basic meaning. The concept of sorting made me ask the question, but the issue I'm interested in is a little more basic. –  May 03 '10 at 18:16
10

You can't create a vector of const ints, and it'd be pretty useless even if you could. If i remove the second int, then everything from there on is shifted down one -- read: modified -- making it impossible to guarantee that v[5] has the same value on two different occasions.

Add to that, a const can't be assigned to after it's declared, short of casting away the constness. And if you wanna do that, why are you using const in the first place?

cHao
  • 84,970
  • 20
  • 145
  • 172
  • My use case is that I want vector of constant values sorted one way and then later I want to change the order, but not the values. –  May 03 '10 at 16:11
  • 4
    You may want to consider making the vector const, then, and casting away the constness whenever you need to modify it (and know you won't be messing up the values). Or wrap your own class around the vector, and regulate the access yourself, since what you're talking about isn't quite a "vector" anymore, in the STL sense of the word -- half of its functionality is gone. – cHao May 03 '10 at 16:20
  • Casting away constness is most likely to leave me in IB or UB land. I take your point that what I'm asking about isn't really a vector, though. –  May 03 '10 at 16:35
  • @Neil: Sometimes changing the order will change the value. For example, if an object contained a pointer to itself then moving the object will also modify it. – tloach May 03 '10 at 18:14
7

You're going to need to write your own class. You could certainly use std::vector as your internal implementation. Then just implement the const interface and those few non-const functions you need.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
4

Although this doesn't meet all of your requirements (being able to sort), try a constant vector:

int values[] = {1, 3, 5, 2, 4, 6};
const std::vector<int> IDs(values, values + sizeof(values));

Although, you may want to use a std::list. With the list, the values don't need to change, only the links to them. Sorting is accomplished by changing the order of the links.

You may have to expend some brain power and write your own. :-(

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • Unfortunately the performance of `vector` and `list` are so different it's not rarely possible to use one for another. – Matthieu M. May 03 '10 at 17:59
  • Although performance wasn't mentioned in the OP's question, sometimes quality and robustness are more important than performance. – Thomas Matthews May 04 '10 at 19:30
2

I would have all my const objects in a standard array.
Then use a vector of pointers into the array.
A small utility class just to help you not have to de-reference the objects and hay presto.

#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>


class XPointer
{
    public:
        XPointer(int const& data)
            : m_data(&data)
        {}

    operator int const&() const
    {
        return *m_data;
    }

    private:
        int const*  m_data;

};

int const               data[]    =  { 15, 17, 22, 100, 3, 4};

std::vector<XPointer>   sorted(data,data+6);


int main()
{
    std::sort(sorted.begin(), sorted.end());
    std::copy(sorted.begin(), sorted.end(), std::ostream_iterator<int>(std::cout, ", "));
    int x   = sorted[1];
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
1

I'm with Noah: wrap the vector with a class that exposes only what you want to allow.

If you don't need to dynamically add objects to the vector, consider std::tr1::array.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
1

It is true that Assignable is one of the standard requirements for vector element type and const int is not assignable. However, I would expect that in a well-thought-through implementation the compilation should fail only if the code explicitly relies on assignment. For std::vector that would be insert and erase, for example.

In reality, in many implementations the compilation fails even if you are not using these methods. For example, Comeau fails to compile the plain std::vector<const int> a; because the corresponding specialization of std::allocator fails to compile. It reports no immediate problems with std::vector itself.

I believe it is a valid problem. The library-provided implementation std::allocator is supposed to fail if the type parameter is const-qualified. (I wonder if it is possible to make a custom implementation of std::allocator to force the whole thing to compile.) (It would also be interesting to know how VS manages to compile it) Again, with Comeau std::vector<const int> fails to compiler for the very same reasons std::allocator<const int> fails to compile, and, according to the specification of std::allocator it must fail to compile.

Of course, in any case any implementation has the right to fail to compile std::vector<const int> since it is allowed to fail by the language specification.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
1

If constness is important to you in this instance I think you probably want to work with immutable types all the way up. Conceptually you'll have a fixed size, const array of const ints. Any time you need to change it (e.g. to add or remove elements, or to sort) you'll need to make a copy of the array with the operation performed and use that instead. While this is very natural in a functional language it doesn't seem quite "right" in C++. getting efficient implementations of sort, for example, could be tricky - but you don't say what you're performance requirements are. Whether you consider this route as being worth it from a performance/ custom code perspective or not I believe it is the correct approach.

After that holding the values by non-const pointer/ smart pointer is probably the best (but has its own overhead, of course).

philsquared
  • 22,403
  • 12
  • 69
  • 98
1

I've been thinking a bit on this issue and it seems that you requirement is off.

You don't want to add immutable values to your vector:

std::vector<const int> vec = /**/;
std::vector<const int>::const_iterator first = vec.begin();

std::sort(vec.begin(), vec.end());

assert(*vec.begin() == *first); // false, even though `const int`

What you really want is your vector to hold a constant collection of values, in a modifiable order, which cannot be expressed by the std::vector<const int> syntax even if it worked.

I am afraid that it's an extremely specified task that would require a dedicated class.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
0

Using just an unspecialized vector, this can't be done. Sorting is done by using assignment. So the same code that makes this possible:

sort(v.begin(), v.end());

...also makes this possible:

v[1] = 123;

John Dibling
  • 99,718
  • 31
  • 186
  • 324
0

You could derive a class const_vector from std::vector that overloads any method that returns a reference, and make it return a const reference instead. To do your sort, downcast back to std::vector.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Instead of deriving from, I'd prefer wrapping a `std::vector<>`: `delete`-ing the derived object from the heap may cause the base class CTor not to be called. That won't happen with a wrapper. The wrapper however could implement a cast operator, too. – foraidt May 03 '10 at 17:56
  • I beg off, deriving from STL containers is always a bad idea. – Matthieu M. May 03 '10 at 17:57
  • @mxp, deleting the derived object would only be a problem if you were deleting the downcasted pointer. Deleting a pointer of the same type that was allocated with new is always safe. – Mark Ransom May 03 '10 at 18:11
  • @Matthieu, it is true that the STL containers were not specifically made to be derived from; in particular they don't have a virtual destructor. However I think the problems are often overblown. – Mark Ransom May 03 '10 at 18:13
  • 1
    @Mark: I understand your point of view, it's not often that those containers are new'ed anyway. But we here have the choice between inheritance and composition, and as a rule of thumb, whenever I have a choice between 2 relationships, I choose the looser one. Of course since C++ has no concept of delegation it means writing a load of forwarding functions, but that doesn't cost much and I'll be able to change the `vector` for a `deque` at leisure if I so wish. – Matthieu M. May 04 '10 at 06:11
  • @Mark, I'm aware of the conditions that need to be fulfilled. These may be quite rare but that makes them only harder to remember all the time. – foraidt May 04 '10 at 06:37
0

std::vector of constant object will probably fail to compile due to Assignable requirement, as constant object can not be assigned. The same is true for Move Assignment also. This is also the problem I frequently face when working with a vector based map such as boost flat_map or Loki AssocVector. As it has internal implementation std::vector<std::pair<const Key,Value> > . Thus it is almost impossible to follow const key requirement of map, which can be easily implemented for any node based map.

However it can be looked, whether std::vector<const T> means the vector should store a const T typed object, or it merely needs to return a non-mutable interface while accessing. In that case, an implementation of std::vector<const T> is possible which follows Assignable/Move Assignable requirement as it stores object of type T rather than const T. The standard typedefs and allocator type need to be modified little to support standard requirements.Though to support such for a vector_map or flat_map, one probably needs considerable change in std::pair interface as it exposes the member variables first & second directly.

abir
  • 1,797
  • 14
  • 26
  • I remember trying to implement a `flat_map` for fun and ending up abandonning because `pair` is so impractical: you need a `std::pair` within but you'd like to expose a `std::pair` which requires some `reintepret_cast` on the references to work (ugh). I don't know why they chose to expose this pair detail in the map interface but I really wished that day they had not :( – Matthieu M. May 03 '10 at 18:11
0

Compilation fails because push_back() (for instance) is basically

underlying_array[size()] = passed_value;

where both operand are T&. If T is const X that can't work.

Having const elements seem right in principle but in practice it's unnatural, and the specifications don't say it should be supported, so it's not there. At least not in the stdlib (because then, it would be in vector).

n.caillou
  • 1,263
  • 11
  • 15