Is there a way of fixing the size of a vector and still changing the contents?
I have tried making a const vector const std::vector<int> vec(10); but that prevents me from changing the values.
vec[3] = 3; gives a compiler error: assignment of read-only location.
I have also tried with a const reference to a non-const vector
std::vector<int> vec(10);
const std::vector<int>& vecref(vec);
which gives the same compiler error.
I want to be able to fix the vector size either on declaration or after an initialisation stage. I could use an old fashioned array, but I want to be able to use the vector algorithms.
I'm using g++ if that makes any difference.
With C++0x, you can use std::array<>, which is like a good old array, with the added benefit of being an STL container, therefore allowing many std::algorithms.
Alternatively, you may want to try boost::array.
boost::array
std::array
Note that there is also std::tr1::array<>.
edit:
Actually, one of the cases that I hadn't gone into was to grow the vector while reading configuration files and then fix the size after that - DanS
Then, why not this (illustrational):
#include <vector>
int main () {
std::vector<int> foo;
/* ... crunch upon foo ... */
// make a copy vector->vector:
const std::vector<int> bar (foo);
// make a copy any->vector
const std::vector<int> frob (foo.begin(), foo.end());
}
Alternatively, if you need reset() semantics, but want to forbid resize() et al, you could write a container adapter:
template <typename T, typename Allocator = allocator<T> >
class resettable_array {
public:
// container ...
typedef typename std::vector<T,Allocator>::iterator iterator;
typedef typename std::vector<T,Allocator>::const_iterator const_iterator;
...
iterator begin() { return vector_.begin() }
const_iterator begin() const { return vector_.begin(); }
...
void push_back (T const &v) { vector_.push_back (v); }
...
// custom
void reset () { ... }
private:
std::vector<T,Allocator> vector_;
};
See also:
std::vector constructors
Embed it in an object that provides only the operations that you want to allow.
Cheers & hth.,
You can make a const vector of pointers, and change the objects they point to. Not saying this is the right answer, just that it's possible.
Take a look at boost.array, it gives you a fixed size array with vector semantics (with the exception of anything that would change the size of the array).
Related
I know this might overlap with the question What is a “span” and when should I use one?, but I think the answer to this specific part of the question is pretty confusing. On one hand, there are quotes like this:
Don't use it if you have a standard library container (or a Boost container etc.) which you know is the right fit for your code. It's not intended to supplant any of them.
But in the same answer, this statement occurs:
is the reasonable alternative to passing const vector& to functions when you expect your data to be contiguous in memory. No more getting scolded by high-and-mighty C++ gurus!
So what part am I not getting here? When would I do this:
void foo(const std::vector<int>& vec) {}
And when this?
void foo(std::span<int> sp) {}
Also, would this
void foo(const std::span<int> sp) {}
make any sense? I figured that it shouldn't, because a std::span is just a struct, containing a pointer and the length. But if it doesn't prevent you from changing the values of the std::vector you passed as an argument, how can it replace a const std::vector<T>&?
The equivalent of passing a std::vector<int> const& is not std::span<int> const, but rather std::span<int const>. The span itself being const or not won't really change anything, but more const is certainly good practice.
So when should you use it?
I would say that it entirely depends on the body of the function, which you omitted from your examples.
For example, I would still pass a vector around for this kind of functions:
std::vector<int> stored_vec;
void store(std::vector<int> vec) {
stored_vec = std::move(vec);
}
This function does store the vector, so it needs a vector. Here's another example:
void needs_vector(std::vector<int> const&);
void foo(std::vector<int> const& vec) {
needs_vector(vec);
}
As you can see, we need a vector. With a span you would have to create a new vector and therefore allocate.
For this kind of functions, I would pass a span:
auto array_sum(std::span<int const> const values) -> int {
auto total = int{0};
for (auto const v : values) {
total += v;
}
return total;
}
As you can see, this function don't need a vector.
Even if you need to mutate the values in the range, you can still use span:
void increment(std::span<int> const values) {
for (auto& v : values) {
++v;
}
}
For things like getter, I will tend to use a span too, in order to not expose direct references to members from the class:
struct Bar {
auto get_vec() const -> std::span<int const> {
return vec;
}
private:
std::vector<int> vec;
};
Regarding the difference between passing a &std::vector and passing a std::span, I can think of two important things:
std::span allows you to pass only the data you want the function to see or modify, as opposed to the whole vector (and you don't have to pass a start index and an end index). I've found this was much needed to keep code clean. After all, why would you give a function access to any more data than it needs?
std::span can take data from multiple types of containers (e.g. std::array, std::vector, C-style arrays).
This can of course be also done by passing C-style arrays - std::span is just a wrapper around C-style arrays with some added safety and convenience.
Another differentiator between the two: In order to modify size of the owning vector inside your function (via std::vector::assign or std::vector::clear, for instance), you would rather pass a std::vector& than a std::span, since span doesn't provide those features.
You can modify the contents of a std::span, but you can't change its size.
Is it possible to move a vector<T*> to a vector<const T*> without copying it and without relying on reinterpret_cast<>? I.e.
vector<int*> get() {
return ...;
}
vector<const int*> getConst() {
return whatgoeshere(get());
}
I'm going to attack this from another angle. And address a possible design issue. You didn't specify what comes in the ..., but assuming get populates a vector and then returns it, the solution in my view is to lift the code that does the populating outside of both functions.
template<typename Int>
void do_get(std::vector<Int*>& v) {
// Populate v
}
auto get() {
std::vector<int*> ret;
do_get(ret);
return ret;
}
auto getConst() {
std::vector<const int*> ret;
do_get(ret);
return ret;
}
One source of truth for the populating logic. And while the two original functions are identical, it's negligible. Furthermore on a sane implementation it won't do any superfluous copies, because RVO is amazing.
No.
Although a T* may be trivially converted to a const T*, a container of T* is not "related" to a container of const T*, so there is simply no functionality to do what you ask.
Consider also that such functionality might hypothetically assign a int** to a const int**, which is not permitted (there is no special case provided for when the programmer intended this assignment to take place as part of a swap operation, as far as I know).
Furthermore, a reinterpret_cast would merely be hacking over these facts, giving your program undefined behaviour.
You are stuck with three options:
Copy the vector (O(n))
Make it so that you have the container you wanted in the first place (O(∞))
Make it so that you don't need the new container type at all (O(?))
After years of blindly accepting the fact that std::vector<T>::operator[] const returns const_reference, but, in light of how const works for smart pointers, I'm now beginning to wonder why it and the rest of the STL containers were designed that way. It seems that the "constness" of a const std::vector is being applied both to the vector and its elements, whereas for smart pointers the "constness" only applies to the pointer and not the element to which it's pointing.
To clarify, it seems like there should be a vector-like container where const just means that a user can't change the size of the container, but the elements in the container are mutable. My main question is: Is there something that would prevent this type of container from being "const correct"?
It seems that there are a couple of hackish workarounds by adding an extra layer of indirection (e.g. std::vector<std::unique_ptr<T>> const) to accomplish this, but I'm looking for something a little less awkward in terms of maintenance.
As an aside, if smart pointers were incorporated into the language before the STL containers, would the const accessors still have been defined the way they are today?
To clarify, it seems like there should be a vector-like container where const just means that a user can't change the size of the container, but the elements in the container are mutable.
That's std::array. You set the size at compile time. For setting the size at constructor-time there's the proposed dynarray.
How about this?
#include <vector>
template<class T>
class mut_wrapper {
mutable T value;
public:
template<class... Args>
mut_wrapper(Args... args) : value(args...) {}
operator T&() const { return value; }
};
template<class T>
using mut_vector = std::vector<mut_wrapper<T>>;
void try_modify_value(const mut_vector<int> &v) {
++v[0];
}
// doesn't compile
// void try_push(const mut_vector<int> &v) {
// v.push_back(3);
// }
void try_push_mutable(mut_vector<int> &v) {
v.push_back(3);
}
The forwarding constructor and implicit conversion makes mut_wrapper<T> behave exactly like a T&, and the type alias makes mut_vector easy to use.
As required, const mut_vector<T> values can have their elements mutated but cannot have elements added or removed.
There's also no additional storage overhead or unnecessary pointer indirection, like there would be with unique_ptr.
This approach generalises to other STL containers, but shouldn't be used for keys in ordered or hashed data structures (e.g. keys of unordered_map, set) for obvious reasons.
Say I have this class:
#include <vector>
using namespace std;
class Bag
{
vector<int*> items;
public:
void addItem(int* i)
{
items.push_back(i);
}
const vector<int*> getItems() const
{
return items;
}
};
The problem is I want to prevent changes of the values pointed by the pointers in parameter items. But using the vector returning from getItems I'm allowed to change the pointed values.
Now, in order to fix that I can declare the parameter items as vector<const int*> but then I can't change the values in my class either.
Is there any other way to protect the values but still not using const in the items declarations?
As #KerrekSB pointed out, you need to reconstruct the structure, changing the type of the element to a const one:
std::vector<const int*> getItems() const {
return std::vector<const int*>(items.begin(), items.end());
}
Here the constructor of vector<const int*> utilizes the implicit conversion from int* to const int*. You can't just return items here, because vector<T> is invariant on T. This directly means that there's no way to convert from vector<int*> to vector<const int*> just by a type cast.
To address your comment:
Is this the only way? I feel like creating a copy is inefficient.
As for the copy, it copies only the pointer data, so typically 8 bytes per element. For a vector of pointers to larger structures, it's typically negligible, but for a vector of ints it indeed is a rather large overhead.
Which brings us to the question: why are you storing pointers in the first place? Simply store vector<int>, and then you can just return a const& to it.
If you want to avoid copies and allow read-only access to your std::vector elements, you could instead provide functions to get a const_iterator into the container:
class Bag
{
vector<int*> items;
public:
void addItem(int* i)
{
items.push_back(i);
}
//could just be called begin if that gives the correct idea
vector<int*>::const_iterator itemsBegin() const
{
return std::cbegin(items);
}
vector<int*>::const_iterator itemsEnd() const
{
return std::cend(items);
}
};
Then you would loop over the Bag contents using that iterator rather than getting out a whole vector of items. If you provided begin and end functions then you could even use range-based for-loops.
For my GUI i need a class with the following purposes to manage controls (windows, buttons etc)
random access to the elements by [index]
random access to the elements by ["key"]
pointer stability, so ptr=&container[index] wont change if elements are added or erased
copy safety. All elements must by stored in the container and copied if '=' is used like container2=conatiner1 (deep-copy)
the order of the elements in the list must be changeable, yet the pointers to the elements must remain valid. If ptr1=container[1] and ptr2=container[2], then after swapping the order of 1 and 2, ptr1==container[2] and ptr2==container[1]
I came to the conclusion that std::list provides the stability for the pointers that I need and std::vector the random access. So I have the idea to store a tuple of std string and iterator in a vector. However, the iterators are all invalid after the container is copied.
Any suggestions on how to tackle this problem best?
Here the main code of the current approach (only important parts are included):
template < class T >
class ControlList
{
struct Tuple{std::string first;typename std::list<T>::iterator second;};
std::vector<Tuple> list;
std::list<T> objects;
inline T& operator [](int i)
{
return *list[i].second;
}
inline T& operator [](std::string s)
{
loopi(0,vlist.size())
if(s==vlist[i].first)
return *vlist[i].second;
}
}
The string access is slow, but usually the container has not more than 10 elements and its used rarely in the program.
Update:
The shared pointer is already good, but cannot solve for the deep copy I need. Lets say I have window2=window1. Now if I have a shared pointer, then pushing a button in window2 also pushes the same button in window1, which is not wanted. I really need a new instance of all objects contained in the container.
Is it possible to override the copy constructor to create new instances of the objects referenced by the smart pointers ?
Both, windows and buttons are stored in a ControlList , where a window contains multiple lists.
Update2:
Overriding the copy constructor and the assignment constructor has apparently solved the problem
Update3:
I just released the GUI this class was intended for under MIT.
Download here.
If you were to use std::vector<std::pair<std::string, std::unique_ptr<T>>>, you could copy the items however you wanted to, and the resulting value would just require one more step of indirection to access. This would eliminate much of the complexity you have right now with 3 different structures. As a bonus, the items would also automatically cleanup after itself.
If you require owner-observer semantics with the pointers, you could instead opt for std::shared_ptr<T> and std::weak_ptr<T>. Shared pointers can easily create weak pointers, which act as non-owning observers which do not affect the referencing counting of the shared pointer.
Edit: Just to add on, shared_ptr and the other smart pointers are C++11 and later-exlcusive. If you desire C++03-compatible solutions, you can look to past Boost implementations or perhaps create one yourself by observing the C++11/14 spec.
Edit2: Here is some code to assist:
http://coliru.stacked-crooked.com/a/a9bf52e5428a48af
#include <vector> //vector
#include <memory> //smart pointers
#include <utility> //pair
#include <string> //string
#include <iostream>//cout
template <class T>
class Container {
public:
inline void push(const std::string& s, const T& t) {
objects.push_back(std::pair<std::string, std::shared_ptr<T>>(s, std::make_shared<T>(t)));
}
inline T& operator [](const size_t& i)
{
return *(objects[i]->second);
}
inline T& operator [](const std::string& s)
{
for (auto it : objects) {
if(s == it.first) {
return *(it.second);
}
}
//welp, what do you do here if you can't find it?
}
private:
std::vector<std::pair<std::string, std::shared_ptr<T>>> objects;
};
int main() {
Container<int> cont;
std::string str {"hi"};
int i {2};
cont.push(str, i);
//This is good...
std::cout << cont["hi"] << std::endl;
//But undefined behavior!
std::cout << cont["02"] << std::endl;
return 0;
}