How to remove constness of const_iterator?
There is a solution with constant time complexity in C++11: for any sequence, associative, or unordered associative container (including all of the Standard Library containers), you can call the range-erase member function with an empty range:
template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
return c.erase(it, it);
}
The range-erase member functions have a pair of const_iterator
parameters, but they return an iterator
. Because an empty range is provided, the call to erase does not change the contents of the container.
Hat tip to Howard Hinnant and Jon Kalb for this trick.
Converting iterators and const_iterators
Both the methods you quote require non-const access to the container, so you can't get access to const underlying elements as non-const.
What you are suggesting doesn't, so it can be UB [dcl.type.cv]
How to remove constness of const_iterator?
There is a solution with constant time complexity in C++11: for any sequence, associative, or unordered associative container (including all of the Standard Library containers), you can call the range-erase member function with an empty range:
template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
return c.erase(it, it);
}
The range-erase member functions have a pair of const_iterator
parameters, but they return an iterator
. Because an empty range is provided, the call to erase does not change the contents of the container.
Hat tip to Howard Hinnant and Jon Kalb for this trick.
How do I escape the const_iterator trap when passing a const container reference as a parameter
If I understand what you're saying correctly, you're trying to use const
to indicate to the caller that your function will not modify the collection, but you want the caller (who may have a non-const
reference to the collection) to be able to modify the collection using the iterator you return. If so, I don't think there's a clean solution for that, unless the container provides a mechanism for turning a const
interator into a non-const
one (I'm unaware of a container that does this). Your best bet is probably to have your function take a non-const
reference. You may also be able to have 2 overloads of your function, one const
and one non-const
, so that in the case of a caller who has only a const
reference, they will still be able to use your function.
How to remove code duplication const_iterator and iterator in my case?
One way is to implement the bulk in const_iterator
and to let iterator
inherit from that. Some const_cast
s is bound to be required.
Another way is implement the iterator as a template, iterator_impl<>
and then add two typedef
s:
using iterator = iterator_impl<value_type>;
using const_iterator = iterator_impl<const value_type>;
I've opted for the latter in this answer and made it possible to convert an iterator
to a const_iterator
, but not the other way around. I have commented on my changes in the code:
#include <cstddef>
#include <iterator>
#include <numeric>
#include <unordered_map>
#include <utility>
template<typename K, typename V>
class MultiMap {
private:
// these constants can be hidden in here as private:
static constexpr size_t MAP_NUM_BITS = 5;
static constexpr size_t MAP_NUM = 1ULL << MAP_NUM_BITS;
using MapType = std::unordered_map<K, V>;
public:
// some of the common public typedef's one expects:
using value_type = typename MapType::value_type;
using reference = typename MapType::reference;
using const_reference = typename MapType::const_reference;
using pointer = typename MapType::pointer;
using const_pointer = typename MapType::const_pointer;
private:
using MapIter = typename MapType::iterator;
using MapConstIter = typename MapType::const_iterator;
// The iterator template - it's private since it doesn't need to be
// seen from the outside.
template<class Type>
struct iterator_impl {
// Added a default and a converting constructor - this became necessary
// when I added the constructor to convert from an iterator to a const_iterator
iterator_impl() = default;
iterator_impl(MapIter It, size_t Idx, MapType* mm) :
it(It), idx(Idx), multi_maps(mm) {}
// Convert from iterator to const_iterator
template<class O, std::enable_if_t<std::is_same_v<O, value_type> &&
!std::is_same_v<O, Type>,
int> = 0>
iterator_impl(const iterator_impl<O>& rhs) :
it(rhs.it), idx(rhs.idx), multi_maps(rhs.multi_maps) {}
// these should probably be made into free friend functions
bool operator==(const iterator_impl& rhs) const { return it == rhs.it; }
bool operator!=(const iterator_impl& rhs) const { return it != rhs.it; }
// I didn't modify operator++
// I made operator->() return a pointer type directly:
auto operator->() { return &*it; }
const_pointer operator->() const { return &*it; }
auto operator*() { return *it; }
const_reference operator*() const { return *it; }
// conditionally selecting the underlying iterator type:
std::conditional_t<
std::is_same_v<Type, const value_type>,
MapConstIter, MapIter> it{};
size_t idx{};
MapType* multi_maps{};
};
public:
// The actual public iterator types a user will use:
using iterator = iterator_impl<value_type>;
using const_iterator = iterator_impl<const value_type>;
// Added cbegin() and cend():
const_iterator cbegin() const {
// Since finding the beginning isn't a simple task, I've chosen to
// reuse the non-const begin() + the conversion to const_iterator here.
// This should simplify maintenance. Note the safe const_cast here:
return const_cast<MultiMap*>(this)->begin();
}
const_iterator cend() const {
// same as above for consistency
return const_cast<MultiMap*>(this)->end();
}
// Now these just call cbegin() and cend():
const_iterator begin() const { return cbegin(); }
const_iterator end() const { return cend(); }
iterator begin() {
size_t idx = 0;
auto it = multi_maps_[idx].begin();
while(it == multi_maps_[idx].end() && idx + 1 < MAP_NUM) {
it = multi_maps_[++idx].begin();
}
return {it, idx, multi_maps_};
}
iterator end() {
return {multi_maps_[MAP_NUM - 1].end(), MAP_NUM - 1, multi_maps_};
}
// I didn't modify find(), insert(), clear() or compute_idx()
size_t size() const {
// Just en example of using a standard algoritm (from <numeric>):
return std::accumulate(
std::begin(multi_maps_), std::end(multi_maps_), size_t(0),
[](size_t current, const auto& m) { return current + m.size(); });
}
private:
MapType multi_maps_[MAP_NUM];
std::hash<K> hasher_{};
};
Copy construction and conversion from iterator
to const_iterator
now works, but conversion from const_iterator
to iterator
fails to compile:
int main() {
MultiMap<int, int>::iterator it1;
MultiMap<int, int>::iterator it2 = it1;
it1 = it2;
MultiMap<int, int>::const_iterator cit1;
MultiMap<int, int>::const_iterator cit2 = cit1;
cit1 = cit2;
cit2 = it1;
// it1 = cit2; // error: no conversion from const_iterator to iterator
}
Assigning a const_iterator to an iterator
You cannot modify a sets elements. Keys must be const
to ensure the invariant promised by the set
: elements are sorted and unique. A map
s elements are sorted too, but you can modify the mapped value of the elements (keys are const
as well). Elements of a std::map<A,B>
are std::pair<const A,B>
.
On cppreference you can read that member aliases for iterators of std::set
are
iterator Constant LegacyBidirectionalIterator
const_iterator Constant LegacyBidirectionalIterator
They are both const iterators.
On the other hand for a std::map
they are:
iterator LegacyBidirectionalIterator
const_iterator Constant LegacyBidirectionalIterator
Assigning a const_iterator
to a non-const one is wrong. Its like trying to cast const
away by assigning a pointer to const
to a pointer to non-const. It won't work because you cannot make something that cannot be modified modifiable. That would break const-correctness.
Removing code duplication from const and non-const methods that return iterators
I believe, it is only possible with the helper
typedef int Z;
class X
{
std::vector<Z> vecZ;
public:
std::vector<Z>::iterator foo(size_t index)
{
return helper(*this);
}
std::vector<Z>::const_iterator foo(size_t index) const
{
return helper(*this);
}
template <typename T>
static auto helper(T& t) -> decltype(t.vecZ.begin())
{
return t.vecZ.begin();
}
};
EDIT
Same can be implemented without c++11
template <typename T>
struct select
{
typedef std::vector<Z>::iterator type;
};
template <typename T>
struct select<const T&>
{
typedef std::vector<Z>::const_iterator type;
};
template <typename T>
static
typename select<T>::type
helper(T t)
{
return t.vecZ.begin();
}
But, well, I think you should think twice before using this approcach
const_iterator and constness of const_iterator::value_type
It allows me to do this:
std::iterator_traits<I>::value_type val = *iter;
val += 5;
doSomething(val);
But that's harder if value_type
is const, because I need to use remove_const
.
If I don't want to get a modifiable value then it doesn't matter whether value_type
is const or not:
const std::iterator_traits<I>::value_type cval = *iter;
std::iterator_traits<I>::reference ref = *iter;
Both of these work for const iterators and non-const iterators, and both work whether value_type
is const or not, but the first example only works for const iterators if their value_type
is non-const.
How are you supposed to take the underlying const correct type of an iterator?
An iterator doesn't necessarily have an underlying type of its own, an iterator usually refers to some range or some collection, and that collection is what has an underlying type. e.g std::list<int>::const_iterator
's value_type
is std::list<int>::value_type
, which is int
not const int
.
You don't necessarily want to know what the underlying type is anyway, it's more likely you want to know what the result of *iter
is, and that's what iterator_traits<I>::reference
tells you.
In C++, is const_iterator the same as const iterator?
No, they are not the same.
Like any const
object, you can not make changes to a const
iterator
:
// ++it gives error below since we try to change it:
for(const std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {}
You are, however, allowed to change the value of the object a const
iterator
is pointing at:
// this is ok (assuming there is at least one element in the vector):
const std::vector<int>::iterator it = vec.begin();
*it = 10;
A const_iterator
is mutable, but you can not change the object that the iterator is pointing at:
// ++it is ok below:
for(std::vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
*it = 10; // error: `*it` is an `int const&`
}
With a const
const_iterator
, you can neither make changes to the iterator nor what it is pointing at.
const std::vector<int>::const_iterator it = vec.begin();
++it; // error
*it = 10; // error
std::cout << *it << '\n'; // ok
A constructed example to illustrate:
struct foo {
using const_iterator = char const*;
using iterator = char*;
const_iterator cbegin() const { return data; };
const_iterator cend() const { return data + sizeof data; };
const_iterator begin() const { return cbegin(); }
const_iterator end() const { return cend(); }
iterator begin() { return data; };
iterator end() { return data + sizeof data; };
char data[2];
};
As seen above, const_iterator
and iterator
are user defined type definitions. Only by convention are the names called what they are called. Using these conventional names also helpes when creating containers that are supposed to work well with other classes, since these type definitions are often used to create or specify instances/types. The names const_iterator
and iterator
do not give these alias any special properties though. It's all up to the creator of the class.
Related Topics
How to Disassemble a Binary Executable in Linux to Get the Assembly Code
How to Check What Shared Libraries Are Loaded at Run Time for a Given Process
Error with Multiple Definitions of Function
Cannot Call Member Function Without Object
Is the Pointer Guaranteed to Preserve Its Value After 'Delete' in C++
Linking Libstdc++ Statically: Any Gotchas
How to Print to the Debug Output Window in a Win32 App
Force C++ Structure to Pack Tightly
Gcc/G++: "No Such File or Directory"
Should I Include <Xxxx.H> or <Cxxxx> in C++ Programs
C++ Inlining Class Methods Causes Undefined Reference
Scope of Exception Object in C++
Multithreaded Rendering on Opengl