Container begin / end / cbegin / cend semantics, iterator / const_iterator compatibility - c++

I've been working on a custom ReversibleContainer, and I thought I was on the right track, but I've hit a snag in testing while going through the semantics of the Container named requirements which makes me think I've fundamentally mis-implemented this. I'm working with C++17.
In particular, my current implementation is formed somewhat like this (pardon errors, I'm condensing it to an example as I type here), where:
Item is the type the container holds
element is the type iterators dereference to (it's convertible to Item)
struct is used for overall brevity in this snippet
only the types and members that I think are relevant are included
struct my_container {
using value_type = Item;
using reference = value_type &;
using const_reference = const value_type &;
using size_type = std::vector<Item>::size_type;
using difference_type = std::vector<Item>::difference_type;
struct element {
// ...
};
// V is value type, D is part of forward/reverse iterator control
template <typename V, int D> struct iterator_ {
using iterator_category = std::random_access_iterator_tag;
using value_type = V;
using reference = V &;
using pointer = V *;
using difference_type = my_container::difference_type;
iterator_ (); // custom
iterator_ (const iterator_<V,D> &) = default;
iterator_ (iterator_<V,D> &&) = default;
~iterator_ () = default;
iterator_<V,D> & operator = (const iterator_<V,D> &) = default;
iterator_<V,D> & operator = (iterator_<V,D> &&) = default;
bool operator == (const iterator_<V,D> &) const;
// ...
};
using iterator = iterator_<element, 1>;
using const_iterator = iterator_<const element, 1>;
using reverse_iterator = iterator_<element, -1>;
using const_reverse_iterator = iterator_<const element, -1>;
iterator begin ();
iterator end ();
const_iterator cbegin () const;
const_iterator cend () const;
reverse_iterator rbegin ();
reverse_iterator rend ();
const_reverse_iterator crbegin () const;
const_reverse_iterator crend () const;
};
Now, I'm looking at the operational semantics of begin, end, cbegin and cend (where a is a my_container, and C is its type):
expression
return type
semantics
a.begin()
(const_)iterator
iterator to the first element of a
a.end()
(const_)iterator
iterator to one past the last element of a
a.cbegin()
const_iterator
const_cast<const C&>(a).begin()
a.cend()
const_iterator
const_cast<const C&>(a).end()
And the problem with my current implementation is that this expression, derived from the cbegin (and likewise cend), is invalid:
a.cbegin() == const_cast<const my_container&>(a).begin()
Because my iterator and const_iterator types are incompatible due to the const being wrapped up in the iterator type via the template parameters to iterator_, and also because my begin() is not const. And now I'm getting that sinking feeling that I have a fundamental flaw in my implementation.
The second problem with my current implementation is that the requirements list the return type of begin and end as "(const_)iterator", and I am only just noticing the "(const_)" now. However, my begin and end do not return a const_iterator.
My conclusion, then, is that my implementation does not meet the operational semantics requirements of Container, and is therefore invalid in its current form. And now I'm sad. :(
So, I'm confused about:
General compatibility requirements of iterator and const_iterator.
The cv-qualifiers on the declaration of begin() and end().
And my questions are:
Am I correct in my conclusion that my container currently fails to meet the requirements of Container wrt. begin, end, cbegin, and cend?
Do the iterator and const_iterator types need to be equality comparable with each other?
Does const_iterator need to be copy constructible and assignable from an iterator?
Do begin() and end() have to be declared as const?
Did I make a mistake in wrapping up the const in iterator_::value_type?
What does "(const_)iterator" mean for the return type of begin and end?
I realize that looks like a lot of questions but they all sort of boil down to the single question of what the requirements for interoperability between iterator and const_iterator are. I hope this post makes sense.

iterator begin ();
iterator end ();
const_iterator begin () const;
const_iterator end () const;
const_iterator cbegin () const;
const_iterator cend () const;
And yes, const_iterator it = iterator; should work (but not the other way around), as should == (I am not certain the first is mandated, but you should still do it).
Also consider writing SCARY iterators, where the iterator is not a subtype of the container.
template<class T>
struct foo {
template<class U, std::enable_if_t<std::is_same_v<std::remove_cv_t<U>, std::remove_cv_t<T>>,bool> =true>
friend bool operator==( foo const& lhs, foo<U> const& rhs );
};
This is an example of a == that works between types.

Related

reference with const access

Using pointers, I can create all combinations of (const/non_const) to (T / const T) for any type T, as discussed in detail this answer.
But using references, how can I define a reference to a variable which can be dereferences (like an iterator), such that the dereferenced reference gives a const access?
For example, consider this program:
#include <array>
int main()
{
std::array<int, 3> a{0, 0, 0};
auto const& iter = a.begin();
// iter++; // Error!
(*iter)++;
}
iter is const variable, and if you uncomment the line iter++ the code cannot compile.
But iter dereferences to a non-const int, which in fact can be incremented with (*iter)++. In a sense, iter mimics int * const, i.e., a const pointer to int.
Is it possible to instead have a (const or non_const) reference iter, such that *iter is a const int?
Please bear in mind that I am aware that I could use const auto& iter = a.cbegin() in the example above. The purpose of the question is how to define iter, such that, when dereferenced, give a const access, irrespective of what is on the right of the equal operator.
For iterators I can imagine to explicitly bind to a const_iterator, like:
const decltype(a)::const_iterator& iter = a.begin();
This solution works because I know that the equivalent "const-access" of an iterator is a const_iterator.
But, more generally:
(some type)& iter = rhs
If all I know is that *rhs is a int, is it possible to find out the type of iter, such that *iter is a const int?
Another point worth to consider is the possible cost of the conversion of rhs.
Is it possible to instead have a (const or non-const) reference iter, such that *iter is a const int?
You call the std::array::cbegin() member function that will return a std::array<int, 3>::const_iterator. Dereferencing that will give you a const int&.
You can however not take the const_iterator by a non-const reference. You must take it by value or by const& (to prolong the life of the temporary returned by the cbegin() function). The general recommendation is to take iterators by value though.
is it possible to find out the type of iter, such that *iter is a const int?
If you know that iter is some kind of pointer (const T*, const T*&, T* or T*& etc.) then you could get a const T* like this:
const std::remove_cvref_t<decltype(iter)> constiter = iter;
For the general case, getting a const_iterator from an iterator without using the container's typedef for const_iterator, will be a bit cumbersome. One way could be to wrap the iterator in an interator class that only returns const&s when dereferenced.
Here's an example with a std::list<int>::iterator which is a little more complicated than a pointer. It works for pointers too though.
#include <iostream>
#include <list>
template<class It>
struct const_iterator {
using IterType = std::remove_reference_t<It>;
using Traits = std::iterator_traits<IterType>;
using difference_type = typename Traits::difference_type;
using value_type = const typename Traits::value_type;
// ^^^^^
using pointer = value_type*;
using reference = value_type&;
using iterator_category = typename Traits::iterator_category;
const_iterator(It nc) : value(nc) {}
// dereferencing gives a const&
reference operator*() const { return *value; }
bool operator==(const const_iterator& rhs) const { return value == rhs.value; }
bool operator!=(const const_iterator& rhs) const { return !(*this == rhs); }
const_iterator& operator++() { ++value; return *this; }
const_iterator operator++(int) { const_iterator rv(*this); ++value; return rv; }
// Add conditional support for proxy member functions supported
// by the original iterator type / iterator_category
IterType value;
};
template<class It>
const_iterator(It) -> const_iterator<It>;
int main()
{
std::list<int> a{1, 2, 3};
auto iter = a.begin();
*iter = 10; // Ok
const_iterator cit(iter); // make a const_iterator from the original iterator
++cit; // Ok
std::cout << *cit << '\n'; // prints 2
//*cit = 20; // Error: *cit is a `const int&`
}
The purpose of the question is how to define iter, such that, when dereferenced, give a const access
And this is where const iterators come into play. For iterators it's actually a less global question, because a const iterator merely means a standard iterator to a constant container:
const std::array<int, 3> a{ 0, 0, 0 };
static_assert(
std::is_same_v<decltype(a.begin()), decltype(a.cbegin())>,
"The iterators are of different type"
);
Constness of the iterators' own types in this case doesn't actually depend on whether they point to a const type or not. As you already noticed, const iterator instead means that the iterator itself cannot be changed and point to a different object.
Answering your second question:
Is it possible to instead have a (const or non_const) reference to const int?
No, a reference is always const implicitly and cannot be rebound after being bound to a variable.

How does the compiler deduce which version of std::vector::begin() to call when passing it to std::vector::insert?

I am trying to make my own mini-vector class and I am attempting to replicate some of the functions, but I can not get them to behave the same way when passing calls such as begin() and end() as parameters - the compiler doesn't deduce the right version. Here is an example:
template<typename T>
class Iterator
{
public:
Iterator() {}
};
template<typename T>
class ConstIterator
{
public:
ConstIterator() {}
};
template <typename T>
class MyList {
public:
MyList() {}
Iterator<T> Begin()
{
return Iterator<T>();
}
ConstIterator<T> Begin() const
{
return Iterator<T>();
}
void Insert(ConstIterator<T> it)
{
}
};
int main() {
MyList<int> myList;
myList.Insert(myList.Begin());
}
At myList.Insert(myList.Begin()); it does not try to use the correct version of Begin(), the const one.
From what I can tell in the std::vector implementation, there are two versions of begin() - one returns an iterator and one returns a const_iterator. The only other difference between them is that one is a const method (the one returning a const_iterator).
_NODISCARD _CONSTEXPR20 iterator begin() noexcept {
auto& _My_data = _Mypair._Myval2;
return iterator(_My_data._Myfirst, _STD addressof(_My_data));
}
_NODISCARD _CONSTEXPR20 const_iterator begin() const noexcept {
auto& _My_data = _Mypair._Myval2;
return const_iterator(_My_data._Myfirst, _STD addressof(_My_data));
}
Many methods, like std::vector::insert take a const_iterator parameter:
_CONSTEXPR20 iterator insert(const_iterator _Where, const _Ty& _Val) { // insert _Val at _Where
return emplace(_Where, _Val);
}
_CONSTEXPR20 iterator insert(const_iterator _Where, _Ty&& _Val) { // insert by moving _Val at _Where
return emplace(_Where, _STD move(_Val));
}
However, there is nothing in the insert method that would make the compiler use the const version of begin().
Which means it has to deduce by the return type alone, but as far as I know that's not possible?
How is it achieving it then?
There is no deduction. If myList is not const-qualified, then the non-const version of Begin() is called for myList.Begin(). Otherwise the const version is called. How you use the result of myList.Begin() is not relevant.
The standard library avoids your issue by providing a conversion from the non-const iterator to the const iterator. For example you could give ConstIterator a constructor which accepts a Iterator, which you must have anyway to make the return Iterator<T>(); statement in your const version of Begin() work (assuming that is not a typo).

Bidirectional iterator implementation

By some reasons I need to implement a bidirectional iterator, after some time, I got this result (add parameter tells what the side the iterator should move to (to avoid code duplication, when implementing reverse_iterator)):
#include <iterator>
namespace gph {
template <typename T, int add> class BidirectionalIterator;
template<typename T, int add>
void swap(BidirectionalIterator<T, add>& it1, BidirectionalIterator<T, add>& it2) {
it1.swap(it2);
}
template<typename T, int add>
class BidirectionalIterator {
private:
T *currentPosition, *begin, *end;
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::bidirectional_iterator_tag;
inline BidirectionalIterator(T* currentPosition, T* begin, T* end):currentPosition(currentPosition), begin(begin), end(end) {}
//copy constructor
inline BidirectionalIterator(const BidirectionalIterator& iterator)
:BidirectionalIterator(iterator.currentPosition, iterator.begin, iterator.end) {}
//move constructor
inline BidirectionalIterator(BidirectionalIterator&& iterator) noexcept
:BidirectionalIterator(iterator.currentPosition, iterator.begin, iterator.end) {}
//copy and move assignment statement
inline BidirectionalIterator& operator=(BidirectionalIterator iterator) {
gph::swap(*this, iterator);
}
inline void swap(BidirectionalIterator& iterator) {
std::swap(currentPosition, iterator.currentPosition);
std::swap(begin, iterator.begin);
std::swap(end, iterator.end);
}
inline reference operator*() const {
return *currentPosition; //dangerous if the iterator is in not-dereferenceable state
}
inline BidirectionalIterator& operator++() {
if (currentPosition != end) currentPosition += add;
return *this;
}
inline bool operator==(const BidirectionalIterator& iterator) const {
return currentPosition == iterator.currentPosition;
}
inline bool operator!=(const BidirectionalIterator& iterator) const {
return !(*this == iterator);
}
inline BidirectionalIterator operator++(int) {
BidirectionalIterator past = *this;
++*this;
return past;
}
inline BidirectionalIterator& operator--() {
if (currentPosition != begin) currentPosition -= add;
return *this;
}
inline BidirectionalIterator operator--(int) {
BidirectionalIterator past = *this;
--*this;
return past;
}
};
}
I've tried to satisfy MoveAssignable, MoveConstructible, CopyAssignable, CopyConstructible, Swappable, EqualityComparable, LegacyIterator, LegacyInputIterator, LegacyForwardIterator, LegacyBidirectionalIterator named requirements.
Some of their requirements are expressed in operators overloading, but some ones from them I do not know how to implement (perhaps, they are automatically implemented by other ones?), for instance: i->m or *i++ (from here). First question: how should I implement them?
Second question: is my iterator implementation good? What are its drawbacks, where did I make mistakes?
P.S. The questions are on edge of unconstructiveness, but I really need help with it. Sorry for my english.
I find it hard to find a definitive answer to this, so just some thoughts, which may be uncomplete and are open to discussion.
i->m can be implemented by inline pointer operator->() { return this->currentPosition; }
*i++ should already be covered by your implementation
I don't see any reason to swap all the pointers in operator=. For three reasons:
You are swapping the values with a local variable
The move constructor doesn't swap any values (would be inconsistent behaviour between BidirectionalIterator newIt=oldIt; and BidirectionalIterator newIt(oldIt);, but it actually isn't because of the previous point)
Those pointers are not unique resources, so there is no problem in copying them and sharing them between multiple instances
operator= is missing a return.
You have using difference_type = std::ptrdiff_t; but don't implement operator- which would return difference_type, why not implement it?
Reverse iterators can be implemented easier by std::reverse_iterator which will just wrap your iterator and invert ++ and -- and so on.
You probably want to find an easy way to implement a const version of the iterator (a version that always returns a const T& or const T*). I saw three versions of this:
duplicating all the code
using const cast
using an additional template parameter bool TIsConst and using pointer = std::conditional_t<TIsConst, const T*, T*>;
using a templated iterator with parameter const T on the other hand might appear easy, but fails to satisfy a requirement, see this question

Converting iterators and const_iterators

General context:
I am trying to build a container that will behave as as wrapper around a multi-dimensional array of run time defined dimensions - in fact the underlying array is of course a 1D array of the total size. The main part is that operator [] returns a wrapper on the sub array.
As containers need iterators, I am currently implementing iterators on that container, both Container::iterator and Container::const_iterator. I try hard to mimic standard container iterators, and they should respect all the requirements for random access and output iterators.
I have already noted the following requirements:
a public default constructor
(of course copy and move semantics)
implicit conversion from an iterator to a const_iterator
iterator and const_interator should be comparable
Specific context:
Standard containers iterators provide no conversion at all from a const_iterator to an iterator, because removing constness can be dangerous. I have already searched SO for that problem and found How to remove constness of const_iterator? where answers propose differents tricks to remove constness from an operator. So I now wonder whether I should implement an explicit conversion from a const_iterator to an iterator ala const_cast on pointers.
Question:
What are the risks in implementing an explicit conversion from a const_iterator to a (non const) iterator and how is it different from the solutions from the linked question (copied here for easier reading):
using advance and distance (constant time form my random access iterators)
iter i(d.begin());
advance (i,distance<ConstIter>(i,ci));
using erase:
template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
return c.erase(it, it);
}
For references, here is a simplified and partial implementation of my iterators:
// Base for both iterator and const_iterator to ease comparisons
template <class T>
class BaseIterator {
protected:
T *elt; // high simplification here...
BaseIterator(T* elt): elt(elt) {}
virtual ~BaseIterator() {}
public:
bool operator == (const BaseIterator& other) {
return elt == other.elt;
}
bool operator != (const BaseIterator& other) {
return ! operator == (other);
}
// other comparisons omitted...
BaseIterator& add(int n) {
elt += n;
return *this;
}
};
// Iterators<T> in non const iterator, Iterator<T, 1> is const_iterator
template <class T, int cnst=0, class U= typename std::conditional<cnst, const T, T>::type >
class Iterator: public BaseIterator<T> {
using BaseIterator<T>::elt;
public:
using value_type = U;
using reference = U*;
using pointer = U&;
using difference_type = int;
using iterator_category = std::random_access_iterator_tag;
Iterator(): BaseIterator<T>(nullptr);
Iterator(T* elt): BaseIterator<T>(elt) {}
// conversion from iterator to const_iterator
template <class X, typename = typename std::enable_if<
(cnst == 1) && std::is_same<X, T>::value>::type>
Iterator(const BaseIterator<X>& other): BaseIterator<X>(other) {};
// HERE: explicit conversion from const_iterator to non const
template <class X, typename = typename std::enable_if<
std::is_same<X, T>::value && (cnst == 0)>::type>
explicit Iterator(const Iterator<X, 1 - cnst>& other): BaseIterator<T>(other) {}
// partial implementation below
U& operator *() {
return *elt;
}
U* operator ->() {
return elt;
}
Iterator<T, cnst, U>& operator ++() {
this->add(1);
return *this;
}
};
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]

C++ custom collection reverse_iterator with similar behaviour to std::vector implementation

I have a template based custom collection (as we cannot use std::vector on the interface). I would like to implement a reverse_iterator specific to this collection. The reverse iterator struct below is a structure nested within the collection class. An iterator (basically a pointer to element type of the collection) is already implemented. This is my first attempt at a reverse iterator.
template <typename T>
struct reverse_iterator
{
typedef T::iterator iterator;
typedef T& reference;
inline reverse_iterator(const iterator & it):_it(it){}
inline reverse_iterator() : _it(0x0) {}
inline iterator base() const {iterator it = _it; return --it;}
inline reverse_iterator operator ++ () {return reverse_iterator(--_it);}
inline reverse_iterator operator -- () {return reverse_iterator(++_it);}
inline reverse_iterator operator ++ (int val) {_it -= val; return reverse_iterator(_it);}
inline reverse_iterator operator -- (int val) {_it += val; return reverse_iterator(_it);}
inline reverse_iterator operator += (int val) {_it -= val; return reverse_iterator(_it);}
inline reverse_iterator operator -= (int val) {_it += val; return reverse_iterator(_it);}
inline reverse_iterator operator + (int val) const {iterator it = _it - val; return reverse_iterator(it);}
inline reverse_iterator operator - (int val) const {iterator it = _it + val; return reverse_iterator(it);}
bool operator == (const iterator & other) const {return other == base();}
bool operator != (const iterator & other) const {return other != base();}
reference operator*() const {return *base();}
iterator operator->() const {return base();}
private:
iterator _it;
};
Is this is workable reverse_iterator or am I missing something ?
Can this be improved?
Except for the things mentioned below your implementation is almost the same as the implementation in libstdc++ (v3, but still somewhat accurate). Note that you're currently missing all non-member functions. All in all you should try to match the std::reverse_iterator interface: if you're ever able to use std types you can happily exchange your mylab::reverse_iterator by std::reverse_iterator.
Missing things
You're missing all comparison operators between reverse_iterator, such as operator==, operator!=, operator< and so on.
Strange things
This is basically a list of stuff where your reverse_iterator differs from the standard one.
Usually the pre-increment/-decrement operators return a reference (*this) and not a new object.
The post increment/decrement operators shouldn't take a value:
inline reverse_iterator operator ++ (int) {
reverse_iterator tmp = *this;
++*this; // implement post-increment in terms of pre-increment!
// or --_it;
return tmp;
}
inline reverse_iterator operator -- (int) { ... }
The compound assignment operators also usually return references.
Your const iterator& constructor should be explicit, otherwise one could accidentally mix reverse and normal iterators.
Instead of a container type T you should use the underlying iterator as template parameter:
template <typename Iterator>
struct reverse_iterator
{
typedef Iterator iterator;
typedef typename iterator_traits<Iterator>::reference reference;
...
}
This enables you to use reverse_iterator on anything that iterator_traits can handle:
template <class Iterator>
struct iterator_traits{
typedef typename Iterator::reference reference;
// Add other things
};
template <class T>
struct iterator_traits<T*>{
typedef T & reference;
};
With this you can even use reverse_iterator<int *> or similar.
operator-> usually returns a pointer to the underlying object, not an intermediary iterator. You might want to add a pointer typedef to both your traits and your original iterator.
It's very uncommon to check equality between different types. Remove the operator==(const iterator&).