Finding it difficult to wrap my head around why I can't have an immutable reference to a mutable object? If I pass a reference to a container like so:
auto f(const map<int, int>& x) -> decltype(non-const map iterator) {...}
It considers x to be const and any iterators I request of it are of the const_iterator type.
I want the following (or so I thought):
auto f(map<int, int>& const x) -> decltype(non-const map iterator)
But it does not compile.
This will not be hard to work around, but I was hoping for some uniformity in my project's code-base regarding const.
Long story short, you can't, in any reliable way, get an iterator out of a const std container.
To illustrate why that is, take a look at how containers are implemented.
template<typename T>
class map
{
struct node {
T data;
node *lchild, *rchild, *parent;
};
public:
iterator begin();
const_iterator begin() const;
private:
node* head;
};
Containers, due to their nature, needs to treat constness in an indirect way. They typically hold a pointer to memory that holds the objects, qualifying a container with const only affects the pointer and its methods.
// const map looks like
node* const head;
If containers were to naively return an iterator even when it is qualified with const, the compiler won't complain, you didn't syntactically mutate anything in the container after all.
// this is legal but wrong in a const map
head->data = T{};
// constness only prevents this
head = nullptr;
However since a container is conceptually the pointer and the stuff pointed to by the pointer, returning a iterator when qualified with const is semantically wrong. To remedy that, we introduce the const overloads, guaranteeing that a const container will truly be const.
As far as the map is concerned, giving out an iterator instantly breaks the contract that const should afford. There is no way the map can ensure its constness after it gives out an iterator, you may or may not mutate the contents with it.
Related
I have a container class which is essentially (showing relevant parts only)
class Foo {
typedef std::map<int, std::vector<int>> data_t;
data_t data;
struct iterator {
data_t::iterator major_it;
data_t::mapped_type::iterator minor_it;
// ...
};
iterator begin();
iterator end() {
return { data.end(), /* XXX */ };
}
};
As you see, I want to have iterator implemented for this container. The iterator shall go through each node in map and iterator through each element in the vector the node refers to. I have no problem implementing the iterator, but I had some trouble implementing end() for the container.
The iterator consists of two levels of iterators, and for the past-end iterator, major_it will have to be data_t.end(), but I won't have anything to initialize minor_it.
In the same vein, begin() will also be broken when the map is empty.
Any idea?
std::vector::iterator is always invalid when value-initialized:
std::vector<…>::iterator invalid_iter_value = {};
Incidentally, it's unusable when default-initialized (i.e. uninitialized), which might be good enough for you. If major_it is already at the end, then simply don't access minor_it.
std::vector<…>::iterator unusable_iter_value;
However, note that it's also illegal to copy the default-initialized object, so value-initialization is a good idea unless you're customizing the copy constructor and operator.
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.
For fun and profit™, I'm writing a trie class in C++ (using the C++11 standard.)
My trie<T> has an iterator, trie<T>::iterator. (They're all actually functionally const_iterators, because you cannot modify a trie's value_type.) The iterator's class declaration looks partially like this:
template<typename T>
class trie<T>::iterator : public std::iterator<std::bidirectional_iterator_tag, T> {
friend class trie<T>;
struct state {
state(const trie<T>* const node, const typename std::vector<std::pair<typename T::value_type, std::unique_ptr<trie<T>>>>::const_iterator& node_map_it ) :
node{node}, node_map_it{node_map_it} {}
// This pointer is to const data:
const trie<T>* node;
typename std::vector<std::pair<typename T::value_type, std::unique_ptr<trie<T>>>>::const_iterator node_map_it;
};
public:
typedef const T value_type;
iterator() =default;
iterator(const trie<T>* node) {
parents.emplace(node, node->children.cbegin());
// ...
}
// ...
private:
std::stack<state> parents;
// ...
};
Notice that the node pointer is declared const. This is because (in my mind) the iterator should not be modifying the node that it points to; it is just an iterator.
Now, elsewhere in my main trie<T> class, I have an erase function that has a common STL signature--it takes an iterator to data to erase (and returns an iterator to the next object).
template<typename T>
typename trie<T>::iterator trie<T>::erase(const_iterator it)
{
// ...
// Cannot modify a const object!
it.parents.top().node->is_leaf = false;
// ...
}
The compiler complains because the node pointer is read-only! The erase function definitely should modify the trie that the iterator points to, even though the iterator shouldn't.
So, I have two questions:
Should iterator's constructors be public? trie<T> has the necessary begin() and end() members, and of course trie<T>::iterator and trie<T> are mutual friends, but I don't know what the convention is. Making them private would solve a lot of the angst I'm having about removing the const "promise" from the iterator's constructor.
What are the correct const semantics/conventions regarding the iterator and its node pointer here? Nobody has ever explained this to me, and I can't find any tutorials or articles on the Web. This is probably the more important question, but it does require a good deal of planning and proper implementation. I suppose it could be circumvented by just implementing 1, but it's the principle of the thing!
1) All iterators are required to be copy-constructible. Your iterator is Bi-directional, hence is also required to be default-constructible (http://en.cppreference.com/w/cpp/concept/ForwardIterator), although I don't know why. So the default constructor needs to be public but you can do what you like with the const trie<T>* one. I would think it should be private, since the purpose of this class is to provide the user with an iterator over the trie, and so its public interface should be only that of an iterator of the appropriate category. No need for any extra public constructors.
2) erase is a non-const function. The only iterators you can validly pass to it are iterators that refer to the same trie that the function is called on, which means (I think, although I'm not quite certain I've followed your design) the whole hierarchy of parents are non-const objects. So I suspect this is one of those cases where you can const_cast<trie<T>*>(it.parents.top().node). The iterator isn't allowed to use it to modify the trie, which is why you want it to hold a pointer-to-const. But when you hold a non-const pointer to the trie, namely this, you are allowed to modify any part of it you like, and the iterator is just giving you the position to start modifying from.
There might be some more general principle of const-safety you can draw here. One possible case in container::erase(const_iterator) functions is that the const container* you'd get from the iterator is equal to this. In that case the const_cast is certainly both safe and legitimate (as well as unnecessary, because you can just use this, but that's beside the point of whether it's const-correct or not). In your container it is not (in general) equal to this, it points to one of the several trie objects that together make up the hierarchical container that this is part of. The good news is, that whole container is logically const or logically non-const together, hence the const_cast is just as safe and legitimate as if it were all one object. But a bit harder to prove correct, because you have to make sure that in your design the whole hierarchical container genuinely does, as I've assumed, share non-constness.
Non-const methods of trie that wish to modify what a const_iterator points to (what it points to should be within this instance of trie) should just const_cast as needed. You know this is a safe and defined cast because if someone managed to call a non-const instance of this trie, then this instance of trie itself is not const.*
Alternatively you could do the opposite and hold non-const pointer(s) to the trie within the const_iterator, which will require a const_cast upon construction. This is safe for the same reason, above. The const_iterator methods will only provide const access, so the user can't mutate the part(s) of trie that the iterator points to. And if a const_iterator needs to be mutated in a non-const trie method it's okay because the trie must not have been const in the first place.*
The premise of the safety of const_casting here is that it is safe to hold and use a non-const pointer to a const object so long as you do not mutate that object. And it is safe to turn a cast away the constness of a pointer which points to something which wasn't originally declared as const.
*Yes, the caller of the non-const trie method might have const_casted in an undefined way; but in that case the burdon of causing undefined behavior is on their head, not tries.
Should iterator's constructors be public?
At least the copy constructor, yes. Have a look at this chart that describes the traits each type of iterator should have:
http://www.cplusplus.com/reference/iterator/
All iterator types should be copy-constructible, copy-assignable and destructible, so that means they need public copy constructors. Some iterators, for example the RandomAccessIterator should also be default constructible so the default constructor should be public as well.
What are the correct const semantics/conventions regarding the iterator and its node pointer here?
If you want to have an erase then you don't really have a const_iterator, you have a regular iterator. The difference between the two is that if you have a const trie object then you can only get a const_iterator out of it because you're not allowed to modify it in any way.
You can notice this in the STL containers. They tend to have both:
iterator begin();
const_iterator begin() const;
What usually happens is that you implement a const_iterator then:
class iterator : public const_iterator {...};
which implements the one or two non-const functions. This probably means just erase for you, since your operator* is going to stay const.
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);
}
};
I'm a little confuse about meaning of this const keyword
I have a class like this
class ClassA {
public:
typedef std::tr1::shared_ptr<ClassA> ptr;
typedef std::tr1::shared_ptr<const ClassA> const_ptr;
void oper() const;
void oper();
private:
.....
};
int main()
{
std::list<ClassA::const_ptr> const_list;
.....
for(std::list<ClassA::const_ptr>::iterator it = const_list.begin();\
it != const_list.end(); it++)
{
(*it)->oper();
}
return 0;
}
I already get const version of oper() from the code above. So I can't imagine what will I get if I change std::list::iterator to std::list::const_iterator.
Your situation is a bit confusing because there are two levels of indirection (the iterator and the smart pointer), with const being applicable in some way to any of them (and also to the referenced object).
You can apply const:
to the object itself; this means that it cannot be modified;
to the smart pointer; this means that the smart pointer cannot be modified, e.g. cannot be reseated via reset;
in some sense to the iterator, using a const_iterator; this means that it will yield a const reference to the object it refers (=>the smart pointer) and that it cannot be used to modify the sequence it refers to.
Expanding a little:
Remember that a const shared_ptr<const ClassA>& (which is what you get by dereferencing a const_iterator) is different from a shared_ptr<const ClassA>& (which you get from a normal iterator): although on both you cannot modify the pointed object (due to the fact that shared_ptr refers to a const ClassA), on the const one you cannot modify the shared_ptr itself, which e.g. means that you can't reset it to point to another object, you cannot assign another shared_ptr to it, ...
Remember also that const versions of iterators, beside yielding a const reference to what they refer to, also disallow modifying the container through them (e.g. you cannot erase an element via a const_iterator).
Not sure if you understand what void oper() const in class ClassA means: In particular, it means that means is that ClassA::oper() is not allowed to modify any members of ClassA.
Which has little baring on your choice of iterator or const_iterator, that choice would have different implications.