tags:

views:

8069

answers:

7

Is there an elegant way to create and initialize a const std::vector<const T> like const T a[] = { ... } to a fixed (and small) number of values?
I need to call a function frequently which expects a vector<T>, but these values will never change in my case.

In principle I thought of something like

namespace {
  const std::vector<const T> v(??);
}

since v won't be used outside of this compilation unit.

+17  A: 

You either have to wait for C++0x or use something like Boost.Assign to do that.

e.g.:

#include <boost/assign/std/vector.hpp>

vector<int> v;
v += 1,2,3,4,5;
Ferruccio
"Have to" might be bit strong; the two step approach can also work. Although the boost approach is certainly good.
janm
Geussing you meant:#include <boost/assign/std/vector.hpp>
Zac
@Zac: Yes, I did.
Ferruccio
+7  A: 

If you're asking how to initialise a const vector so that it has interesting contents, then the answer is probably to use the copy constructor. First you laboriously fill in a vector, then you create your new const vector from it. Or you can use the vector<InputIterator>(InputIterator, InputIterator) constructor template to initialise from some other kind of container or an array. If an array, then that could have been defined with an initialisation list.

Something like this is hopefully close to what you want:

const T ra[3] = {t1, t2, t3};
const vector<const T> v(ra, ra+3);

If you're asking how to pass a const vector into a function which takes a vector then the answer is either:

  • you can't, because the function might alter the vector and your object/reference is const. Make a non-const copy of the original, and pass that in.

or

  • use const_cast to remove the constness in order to pass it into a function which takes a non-const vector but which you just so happen to know will not modify the vector.

The latter is one of those things which will, quite rightly, cause anyone who sees it to make comments about goggles, and the fact that they do nothing. It's exactly what const_cast is for, but there's a reasonably strong argument that says if you need const_cast, you have already lost.

Doing both of those things (creating a const vector from a non-const one with the copy constructor, and then casting away constness) is definitely wrong - you should have just used a non-const vector. So pick at most one of these to do...

[Edit: just noticed that you're talking about a difference between vector<T> and const vector<const T>. Unfortunately in the STL, vector<const T> and vector<T> are completely unrelated types, and the only way to convert between them is by copying. This is a difference between vectors and arrays - a T** can be silently and safely cast to const T *const *]

Steve Jessop
+6  A: 

As others have said, you can't init a vector the same way you can init a C-style array, unless you give it pointers to a source array. But in that case, if your vector is a global const, why not just use an old C-style array instead?

const int MyInts[] = {
1, 2, 3, 4, 5};

const size_t NumMyInts = sizeof(MyInts)/sizeof(MyInts[0]);

You can even use STL algorithms against this array, the same way you would use algorithms against a const vector...

const int* myInt = std::find( &MyInts[0], &MyInts[NumMyInts], 3);
John Dibling
That's a good point, but he's passing it to a function which expects a vector. He may not be able to modify that function.
Ferruccio
A: 

Not sure if I understood you right. I understand your question like this: you want to initialize a vector to a large number of elements. What's wrong with using push_back() on the vector? :-)

If you know the number of elements to be stored (or are sure that it will store less than the next power of 2) you can do this, if you have a vector of pointers of type X (works only with pointers):

std::vector< X* > v;
v.reserve(num_elems);
X* p = v.begin();
for (int count = 0; count < num_elems; count++)
   p[count] = some_source[count];

Beware of adding more than the next power of 2 elements, even if using push_back(). Pointers to v.begin() will then be invalid.

mstrobl
There are many problems with this code. You can't just call vector::reserve() and then start manipulating pointers and depending on magic implementation details like powers of two. Iterator converted to pointer? What about vector::size()? On what compiler and STL library does this even compile?
janm
I agree this is very bad code. A the very best, you should forget the use of the pointer/iterator and fallback to push_back
paercebal
Nope, that's perfectly legal C++ code. The STL guarantees that this will work: vector will allocate consecutive space for at least 2^n elements. After all 2^n elements vector is allowed to realloc, giving you a new space behind vector::begin(). Sorry, it might be hacky, but it's the standard. :-)
mstrobl
I see the downvotes, but insist that there exist good reasons for using this STL property (it's there for a reason). Hey, there are a lot of doctoral theses out there that use this property :)
mstrobl
Sorry, how, exactly is: "X* p = v.begin();" standard? How is using operator[] to access out of bounds elements in a vector standard? What is special about pointers that makes this work over (say) integers? Where does this code even compile?
janm
Janm, the STL defines this behaviour as standard behaviour, like it or not. Go ahead and try on your C compiler (incl missing asterisk :). It is useful if you want the power of std::vector and need the data pointed to consecutively, such as when sending vertex data to a graphics card.
mstrobl
Just tried on VC2005 and gcc 3.4.6. Failed, first error on "X** p = v.begin();" You have to change it to "X** p = " to even get it to compile. It is still broken; what do you expect v.size() to return?
janm
Steve Jessop
... however, I'm deeply unconvinced that using the contiguous memory guarantee to add new values by pointer is standard - for starters, the vector doesn't get a chance to update its own size, so I'm fairly sure the results of this code are either undefined, or a vector of size 0.
Steve Jessop
Steve Jessop
... so your strange choice of loop would probably be valid if you initialize with v(num_elems) (constructor (size_type)) rather than the no-args constructor followed by a call to reserve. But then you might as well just loop setting v[count] instead of messing about with p.
Steve Jessop
Steve Jessop
Thanks for the defense, onebyone! I wrote that very late at night yesterday. The initialization was asked for by the question as I interpreted it.And it's true that this is ugly, but again, been late. The usual way would be the other way around of course, using push_back and then taking [contd]
mstrobl
[contd] advantage of the linear space - many people don't know that. Onebyone obviously does, and that's good to see. A pity that janm would want to just discard my answer. As to where the power of two comes in: http://www.sgi.com/tech/stl/FAQ.html
mstrobl
Of course you can take advantage of contiguous space in the vector; the standard guarantees that. However, calling reserve() is not sufficient to do that; you must add the elements such that size() is correct. You could change the code so that you allocated the correct number of elements ....
janm
janm
... and (3) asserting that it only works for a container with pointers.
janm
I did indeed make a few mistakes with my implementation. As I already wrote, it was late and I was trying to answer the question that I interpreted to be asking for just that. I considered the vector to alrady have a sufficient size but did not point that out. janm's remark (3) points out a ..
mstrobl
mstrobl
... hackish voodo in it. There's no "weird magic number" of 2^n as a comment, that I think I remember, which is now gone used to bemoan. So, also seeing the sparked discussion, I do not see how you can sweep away the example merely as "bad code". I still insist that the "-1" is unjustified.
mstrobl
Btw, that SGI FAQ refers only to their implementation. There may be other implementations of STL, which use a constant other than 2, so relying on it is non-standard. Also it only says it doubles capacity each time: if it starts at or is reserve()d to a non-power, then it will remain a non-power.
Steve Jessop
I'd strongly suggest that anyone who wants to write portable C++ grab a copy of the standard - it saves prefacing everything you do with "this works on GCC 4.4 for x86 linux with SGI", and wondering whether that means it will work elsewhere too :-)
Steve Jessop
Oh, and I say "a constant other than 2" because vector guarantees amortised constant-time addition, and I don't know of a way of doing that other than multiplying the capacity by some constant each time it's extended. But the constant doesn't have to be 2.
Steve Jessop
Of course the -1 is justified. The only correct statement in the response is "What's wrong with using push_back() on the vector?". Of cours the number of elements to store is known (it's a constant!) and powers of two are irrelevant (and an imp. detail). The rest of the example is just wrong.
janm
In response to my first comment where I pointed out the iterator to pointer conversion and the issue with size() and the powers of two being an implemenation detail your response was "Nope, that's perfectly legal C++ code." The fact that you can access the memory in an already allocated vector ...
janm
... using pointers very little to do with allocating a vector from constants (ie: answering the actual question). And you're code wasn't even doing that. I think you're lucky you've only got -1.
janm
@onebyone: I did "T**" because of the comment from mstrobl: "Go ahead and try on your C compiler (incl missing asterisk :)" which was really the only way it would get close. I agree with your comments. Converting vector<T>::iterator to T* is not standard. Pointer to a deref'd iter is different.
janm
+3  A: 

You can do it in two steps:

namespace {
    const T s_actual_array[] = { ... };
    const std::vector<const T> s_blah(s_actual_array,
        s_actual_array + (sizeof(s_actual_array) / sizeof(s_actual_array[0])));
}

Perhaps not as beautiful as you might like, but functional.

janm
A: 

If they're all the same you can just do

vector<T> vec(num_items, item);

but I assume they're not - in which case the neatest way is probably:

vector<T> vec(num_items);
vec[0] = 15;
vec[1] = 5;
...

C++0x will let you use an initialiser list in exactly the way you're thinking of, but that's not a lot of good right now, unfortunately.

Peter
That won't work for a const vector though.
Adam Pierce
+6  A: 
Shadow2531
why was this marked down?
devin
wicked, but useful for testing, etc. ++
Eli Bendersky