const correctness for containers - c++

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.

Related

Why in particular should I rather pass a std::span than a std::vector& to a function?

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.

Overloading push_back() in vector to allow non-duplicate elements

Can we overload the push_back() method in std::vector to allow non-duplicate elements? I know std::set and std::unordered_set are supposed to avoid duplicate elements, but std::set sorts the elements and std::unordered_set stores the elements in no particular order. I need to retrieve the elements in the order they are inserted, while ensuring duplicate elements are not inserted.
Edit: There's a possible duplicate for this question here. The best solution to this duplicate proposes to have an auxiliary data structure and another custom method "add". This doesn't look good for me since(I'll put it in a separate documentation) the users inserting data in std::vector rarely refer to the documentation for any custom functions. If there's no efficient way though, this can be a last resort.
Many people advise against it, but it seems there's some kind of urban legend going around that doing so will cause the universe to undergo vacuum decay and reality as we know it will dissolve.
You can publicly inherit from std::vector. But you have to think about what you can do with that.
If you inherit from vector, it is highly recommended that you don't add any data members to it. This can cause object slicing (google "c++ object slicing".) You also need to keep in mind that vector is not using virtual functions. That means you cannot override member functions. You can only shadow them, so it's not guaranteed that it will always be your push_back() function that gets called. The original will get called if you pass an object of your class to something that takes a reference to a vector, for example.
So in the end, you'd need to add a push_back_unique() function instead. But that in turns means that can be served by a simple free function instead. So inheriting vector isn't needed. This of course means there's never a guarantee that the elements in the vector will be unique. Other code might use push_back() instead somewhere.
Inheriting vector makes sense if you want to add completely new convenience functions that don't impose or lift any restrictions that vector has. If you want something that looks like a vector but really isn't (because it has different behavior and/or restrictions), you should implement your own type that delegates the container functionality to vector by either inheriting privately from it, or by having it as a private data member, and then replicate the vector API through public wrapper functions.
But this is very tedious to implement. Usually, you don't really need all the API from vector. So I'd say just write a smaller class around vector that only provides the functionality you need. And that functionality sounds like it's going to be pretty much read-only, since allowing write access to the elements allows for setting an element to the same value as another, breaking the container's uniqueness. So you could do something like:
template<typename T>
class UniqueVector
{
public:
void push_back(T&& elem)
{
if (std::find(vec_.begin(), vec_.end(), elem) == vec_.end()) {
vec_.push_back(std::forward(elem));
}
}
const T& operator[](size_t index) const
{
return vec_[index];
}
auto begin() const
{
return vec_.cbegin();
}
auto end() const
{
return vec_.cend();
}
private:
std::vector<T> vec_;
};
If you still want to allow write access to individual elements, then you can provide non-const functions that check if the value that is passed is already in the vector. Like:
void assign_if_unique(size_t index, T&& value)
{
if (std::find(vec_.begin(), vec_.end(), value) == vec_.end()) {
vec_[index] = std::forward(value);
}
}
This is a minimal example. You should obviously add the functions you actually want. Like size(), empty(), and whatever else you need.
You should first define a free function1 to implement your feature:
template<class T>
std::vector<T>&
push_back_unique(std::vector<T>& dest, T const& src)
{ /* ... */ }
If you use this a lot, and if make sense regarding your program, you might want to define an operator to do so:
template<class T>
std::vector<T>& operator<<(std::vector<T>& dest, T const& src)
{ return push_back_unique(dest, src); }
This allows:
std::vector<int> data;
data << 5 << 8 << 13 << 5 << 21;
for (auto n : data) std::cout << n << " "; // prints 5 8 13 21
1) This is because inheriting from standard containers is often bad practice and brings pitfalls.

Move a vector<T*> to vector<const T*>

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(?))

How to make a copy safe container with std::list iterators stored in a std vector in c++?

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

C++11 shared_pointer constness within stl containers

I have the following problem and I wonder whether there's a better way to solve it:
class myObj {
public:
typedef std::shared_ptr<myObj> handle;
typedef std::shared_ptr<const myObj> const_handle;
int someMethod() { ... }
int someConstMethod() const { ... }
};
Now what I need is a container class that somehow allows you to modify or read a collection of myObj depending on its own constness, like so:
class myCollection {
public:
typedef std::list<myObj::handle> objList;
typedef std::list<myObj::const_handle> const_objList;
inline objList& modify() { return _obl; }
// it would be nice to do this, but it won't compile as
// objList and const_objList are completely different types
inline const_objList& read() const { return _obl; } // doh! compile error...
// returning a const objList won't help either as it would return non-const
// handles, obviously.
// so I am forced to do this, which sucks as i have to create a new list and copy
void read(const_objList &l) {
std::for_each(
_obl.begin(),
_obl.end(),
[&l] (myObj::handle &h) { l.push_back(h); }
// ok as handle can be cast to const_handle
); // for_each
}
private:
objList _obl;
};
So this solution actually works as a const myCollection would only allow you to get a list of const_handle which only allows you to call non-modifying methods of myObj (GOOD).
The problem is that the "read" method is really ugly (BAD).
Another method would be to expose somehow the list methods and return const_handle and handle as needed but it's a lot of overhead, especially if you want to use something more complex than a list.
Any idea?
A list-of-pointers-to-T is not a list-of-pointers-to-constant-T.
std::list<std::shared_ptr<int>> a;
std::list<std::shared_ptr<const int>>& ra = a; // illegal but imagine it's not
std::shared_ptr<const int> x = std::make_shared<const int>(42);
ra.push_back(x); // totally legal, right?
++**a.begin(); // oops... just incremented a const int
Now a list-of-pointers-to-T is, conceptually, a constant-list-of-constant-pointers-to-constant-T, but std::list<std::shared_ptr<T>> does not support such a deep const propagation. const std::list<std::shared_ptr<T>> contains constant pointers to non-constant objects.
You can write your own variant of list<> or your own variant of shared_ptr<> that have such support. It probably won't be very easy though. A const_propagating_shared_ptr is probably the easier of the two. It would have to encapsulate an std::shared_ptr<T> object and forward almost everything to it as-is. As opposed to std::shared_ptr<T> it would have separate const and non-const versions of operator->, operator*() and get().
Given what you stated that you want to accomplish, I don't think that your solution is too bad. Imagine that some other code may be modifying the internal collection, like adding or removing values. Returning a copy of the current state of the collection is safe for client code, since it can work on the copy, without the danger of element being deleted in the meantime. But I digress, this is getting into threading issues and may not be relevant.
You could use prettier:
inline const_objList read() const
{
const_objList cl(_obl.begin(), _obl.end());
return cl;
}
However, I do think that your problems derive from mixing two types of constness: constness of the members of the collection versus the constness of the collection itself.
Instead of Modify and Read methods, that deal with the list as a whole, I would try exposing const and non-const iterators to internal list, through corresponding const and non-const methods returning said iterators.
But this immediately begs the question: why then have myCollection in the first place?
Creating entirely new collection type around std::list doesn't seem needed, unless you get a lot of proverbial bang for the buck from other, added functionality that is not visible in your sample.
You can then make your added functionality free methods that take std::list of your handles as the input. Not everything requires an object and operations on objects need not necessarily be member methods, unless access to private data is required.
You mentioned maybe using another container instead of the list. But your class, as is, won't do it, unless you have a template, where template parameter can be one of STL containers.
Which then implies that you should expose iterators.
Namely, if you foresee changing the internal collection type, you would want to make the public interface to myCollection transparent regarding the collection type. Otherwise, clients will have to recompile each time you change your mind about the internal implementation.
EDIT -----
Finally, if implementing iterators (while interesting and most correct) is too much, why not go for simple getters like in this SO post:
smart pointer const correctness
I'll quote the topmost answer by Rüdiger Stevens (it assumes vector instead of list):
template <typename T>
class MyExample
{
private:
vector<shared_ptr<T> > data;
public:
shared_ptr<const T> get(int idx) const
{
return data[idx];
}
shared_ptr<T> get(int idx)
{
return data[idx];
}
void add(shared_ptr<T> value)
{
data.push_back(value);
}
};