I'm trying to implement a reverse-iterator adaptor for my iterator and const_iterator classes with a little bit of trouble. If anyone could guide me through this, that would be greatly appreciated!
The idea is that I should be able to create a reverse-iterator from my rbegin() and rend() function calls
reverse_iterator rbegin();
reverse_iterator rend();
const_reverse_iterator rbegin() const;
const_reverse_iterator rend() const;
I'm using the following typedef's in the class:
typedef btree_iterator<T> iterator;
typedef const_btree_iterator<T> const_iterator;
typedef reverse_btree_iterator<iterator> reverse_iterator;
typedef reverse_btree_iterator<const_iterator> const_reverse_iterator;
As you can see, I would like to be able to create reverse-iterators using templates, giving the reverse_iterator class either an iterator or const_iterator.
Unfortunately, it is this bit I'm stuck on...
Below is the class definition that I currently have, with errors.
template <typename I> class reverse_btree_iterator {
typedef ptrdiff_t difference_type;
typedef bidirectional_iterator_tag iterator_category;
public:
reverse_btree_iterator() : base_(I()) {}
template <typename T> reverse_btree_iterator(const btree_iterator<T>& rhs) : base_(rhs) {}
I base() { return base_; }
I::reference operator*() const;
I::pointer operator->() const;
I& operator++();
I operator++(int);
I& operator--();
I operator--(int);
bool operator==(const I& other) const;
bool operator!=(const I& other) const;
private:
I base_;
};
I've never used templates like this before, so it is very likely I'm completely misunderstanding how they can be used...
Since I can be an iterator or a const_iterator, the typedef of reference and pointer vary between the two classes. The lines that aren't compiling are these:
I::reference operator*() const;
I::pointer operator->() const;
I'm not sure how else I can make the one reverse_iterator class work for both iterator and const_iterator if I'm not able to do I::reference and I::pointer. I also tried adding template in front of those, since they are defined in the iterator class (for example) as:
typedef T* pointer;
typedef T& reference;
reference and pointer are dependent names, so you have to use
typename I::reference operator*() const;
typename I::pointer operator->() const;
In addition, the constructor should accept just the I.
However, there is no need to write this class at all. The standard library has reverse_iterator for this. Or if you are not happy with that, there's also Boost.ReverseIterator.
All it takes is just
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
In addition, you forgot to provide comparison operators with other reverse iterators of the same type. This is a reverse iterator requirement.
Related
C++20 has explicit library support for std::contiguous_iterator_tag. Some STL algorithms (e.g. std::copy) can perform much better on contiguous iterators. However, I'm unclear on exactly how the programmer is supposed to get access to this new functionality.
Let's suppose for the sake of argument that we have a completely conforming C++20 library implementation. And I want to write the simplest possible contiguous iterator.
Here's my first attempt.
#include <iterator>
class MyIterator {
int *p_;
public:
using value_type = int;
using reference = int&;
using pointer = int*;
using difference_type = int;
using iterator_category = std::contiguous_iterator_tag;
int *operator->() const;
int& operator*() const;
int& operator[](int) const;
MyIterator& operator++();
MyIterator operator++(int);
MyIterator& operator--();
MyIterator operator--(int);
MyIterator& operator+=(int);
MyIterator& operator-=(int);
friend auto operator<=>(MyIterator, MyIterator) = default;
friend int operator-(MyIterator, MyIterator);
friend MyIterator operator+(MyIterator, int);
friend MyIterator operator-(MyIterator, int);
friend MyIterator operator+(int, MyIterator);
};
namespace std {
int *to_address(MyIterator it) {
return it.operator->();
}
}
static_assert(std::contiguous_iterator<MyIterator>); // FAILS
This fails on both GCC/libstdc++ and MSVC/STL; but is it supposed to?
For my next attempt, I specialized pointer_traits<MyIterator>. MyIterator isn't actually a pointer, so I didn't put anything in pointer_traits except the one function that the library needs. This is my second attempt:
#include <iterator>
class MyIterator {
~~~
};
template<>
struct std::pointer_traits<MyIterator> {
int *to_address(MyIterator it) {
return it.operator->();
}
};
static_assert(std::contiguous_iterator<MyIterator>); // OK!
Is this how I'm supposed to do it? It feels extremely hacky. (To be clear: my first failed attempt also feels extremely hacky.)
Am I missing some simpler way?
In particular, is there any way for MyIterator itself to warrant that it's contiguous, just using members and friends and stuff that can be defined right there in the body of the class? Like, if MyIterator is defined in some deeply nested namespace, and I don't want to break all the way out to the top namespace in order to open namespace std.
EDITED TO ADD: Glen Fernandes informs me that there is a simpler way — I should just add an element_type typedef, like this! (And I can remove 3 of iterator_traits' big 5 typedefs in C++20.) This is looking nicer!
#include <iterator>
class MyIterator {
int *p_;
public:
using value_type = int;
using element_type = int;
using iterator_category = std::contiguous_iterator_tag;
int *operator->() const;
int& operator*() const;
int& operator[](int) const;
MyIterator& operator++();
MyIterator operator++(int);
MyIterator& operator--();
MyIterator operator--(int);
MyIterator& operator+=(int);
MyIterator& operator-=(int);
friend auto operator<=>(MyIterator, MyIterator) = default;
friend int operator-(MyIterator, MyIterator);
friend MyIterator operator+(MyIterator, int);
friend MyIterator operator-(MyIterator, int);
friend MyIterator operator+(int, MyIterator);
};
static_assert(std::contiguous_iterator<MyIterator>); // OK!
Also, I've seen some stuff about using member typedef iterator_concept instead of iterator_category. Why might I ever want to provide MyIterator::iterator_concept? (I'm not asking for a full historical explanation here; just a simple best-practice guideline "nah forget about iterator_concept" or "yes provide it because it helps with X" would be nice.)
There are two ways to support std::to_address. One is:
namespace std {
template<>
struct pointer_traits<I> {
static X* to_address(const I& i) {
// ...
}
};
}
Note the static above.
The second way, as Glen pointed out to you, is to simply define I::operator-> and to make sure the primary template of std::pointer_traits<I> is valid. This just requires that std::pointer_traits<I>::element_type is valid.
The answer by Nicol Bolas is incorrect because that is not how you customize std::to_address for a user-defined type. Since C++20 (because of P0551) it is forbidden to specialize function templates in namespace std that you're not explicitly allowed to.
You're not allowed to specialize std::to_address. You can provide std::pointer_traits<I>::to_address though, and std::to_address will call it if it exists.
The last version in my question seems to be the most correct. There is just one subtlety to watch out for:
MyIterator::value_type should be the cv-unqualified type of the pointee, such that someone could write value_type x; x = *it;
MyIterator::element_type should be the cv-qualified type of the pointee, such that someone could write element_type *ptr = std::to_address(it);
So for a const iterator, element_type isn't just a synonym for value_type — it's a synonym for const value_type! If you don't do this, things will explode inside the standard library.
Here's a Godbolt proof-of-concept. (It's not a complete ecosystem, in that really you'd want MyIterator to be implicitly convertible to MyConstIterator, and probably use templates to eliminate some of the repetition. I have a rambly blog post on that subject here.)
#include <iterator>
class MyIterator {
int *p_;
public:
using value_type = int;
using element_type = int;
using iterator_category = std::contiguous_iterator_tag;
int *operator->() const;
int& operator*() const;
int& operator[](int) const;
MyIterator& operator++();
MyIterator operator++(int);
MyIterator& operator--();
MyIterator operator--(int);
MyIterator& operator+=(int);
MyIterator& operator-=(int);
friend auto operator<=>(MyIterator, MyIterator) = default;
friend int operator-(MyIterator, MyIterator);
friend MyIterator operator+(MyIterator, int);
friend MyIterator operator-(MyIterator, int);
friend MyIterator operator+(int, MyIterator);
};
static_assert(std::contiguous_iterator<MyIterator>); // OK!
class MyConstIterator {
const int *p_;
public:
using value_type = int;
using element_type = const int;
using iterator_category = std::contiguous_iterator_tag;
const int *operator->() const;
const int& operator*() const;
const int& operator[](int) const;
MyConstIterator& operator++();
MyConstIterator operator++(int);
MyConstIterator& operator--();
MyConstIterator operator--(int);
MyConstIterator& operator+=(int);
MyConstIterator& operator-=(int);
friend auto operator<=>(MyConstIterator, MyConstIterator) = default;
friend int operator-(MyConstIterator, MyConstIterator);
friend MyConstIterator operator+(MyConstIterator, int);
friend MyConstIterator operator-(MyConstIterator, int);
friend MyConstIterator operator+(int, MyConstIterator);
};
static_assert(std::contiguous_iterator<MyConstIterator>); // OK!
One of the main benefits of C++ concepts as a language feature is that the concept definition tells you what you need to provide. std::contiguous_iterator is no different. Yes, you may have to dig through 10+ layers of other concept definitions to get down to the basic terms you need to provide. But they are right there in the language.
To whit, a contiguous iterator is a random access iterator:
Whose tag is derived from contiguous_iterator
Whose reference_type is an lvalue reference to its value_type (ie: not a proxy iterator or generator).
For which the standard library function std::to_address can be called to convert any valid iterator (even one which is not dereferencable) into a pointer either to the element the iterator references or to the one-past-the-end pointer.
Unfortunately, satisfying #3 can't be done by specializing std::to_address directly. You have to use either specialize pointer_traits::to_address for your iterator type or give your iterator an operator-> overload.
The latter is easier to do and, despite operator-> not being otherwise required by std::contiguous_iterator, makes sense for such an iterator type:
class MyIterator
{
...
int const *operator->() const;
};
FYI: Most compilers will give you more helpful error messages if you constrain a template based on the concept, rather than just static_asserting on it. Like this:
template<std::contiguous_iterator T>
void test(const T &t);
test(MyIterator{});
I am writing an iterator class (say MyIterator) for a library.
Is it a good idea to use const overloading to make
const MyIterator acts like a const_iterator of std::vector while MyIterator acts as like iterator of std::vector ?
Will the library user/developer get confused?
The implementation would be like:
// std::iterator example
#include <iostream> // std::cout
#include <iterator> // std::iterator, std::input_iterator_tag
class MyIterator : public std::iterator<std::input_iterator_tag, int>
{
mutable int* p;
public:
MyIterator(int* x) :p(x) {}
MyIterator(const MyIterator& mit) : p(mit.p) {}
MyIterator& operator++() {++p;return *this;}
MyIterator operator++(int) {MyIterator tmp(*this); operator++(); return tmp;}
bool operator==(const MyIterator& rhs) {return p==rhs.p;}
bool operator!=(const MyIterator& rhs) {return p!=rhs.p;}
const int& operator*() const {return *p;} // <-- const overload
int& operator*() {return *p;}
};
An alternative would be using templates to implement a single iterator class that can be specialized into the const and non-const iterators. I am currently doing that (i heard boost is doing that...). But, the templates get complex very quickly as I implement range, and then range of range (as in nested range based for loop).
Using const MyIterator as a substitute of const_MyIterator (const_iterator) won't work, because const_iterator is not meant to be a constant iterator, but an iterator iterating over constant elements.
Also, with a const MyIterator, you couldn't use modifying operators, like ++ or --, because these are non-const methods, modifying the iterator itself.
So, if you want to provide some sort of const_iterator, you won't get around implementing one.
Will the library user/developer get confused?
Finally, to answer your question: Yes, I think so, because of the different behaviour (and expectations) of a const iterator vs const_iterator.
I have implemented a doubly-linked list, and created an iterator which extends std::iterator. I'm now trying to create a const version.
I tried:
typename typedef list_iterator<T_> iterator;
typename typedef list_iterator<T_> const const_iterator;
If I do this though, I get this error:
error C2678: binary '--' : no operator found which takes a left-hand operand of type 'const list_iterator<T_>' (or there is no acceptable conversion)
Here is my operator--:
list_iterator& operator -- ()
{
_current = _current->_previous;
return *this;
}
list_iterator operator--(int) // postfix
{
list_iterator hold = *this;
--*this;
return list_iterator( hold );
}
If I put
list_iterator operator--() const
... I'm not able to modify the value of _current
How do I make my iterator now work like a const_iterator so that from my linked list I can call get the const version of begin() and end(), as well as cbegin() and cend()?
Right. The problem is your declaration of your const_iterator typedef. (See How to correctly implement custom iterators and const_iterators? )
Instead of
typename typedef list_iterator<T_> const const_iterator;
you want
typename typedef list_iterator<const T_> const_iterator;
I'm taking a course where we're being introduced to a few software patterns, one of them being the iterator pattern, and we're being asked to implement it in C++. Our professor gave us some sample code a few weeks ago and I'd like to understand it a little better than I do. First I'll post the code:
template <class T_,std::size_t SIZE_>
class carray {
public:
typedef T_ value_type;
typedef std::size_t size_type;
typedef T_ & reference;
typedef T_ const & const_reference;
typedef T_ * pointer;
typedef T_ const * const_pointer;
typedef T_ * iterator;
typedef T_ const * const_iterator;
typedef std::ptrdiff_t difference_type;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
// data store
value_type data[SIZE_];
size_type size() const { return SIZE_; }
reference operator [] ( size_type idx ) { return data[idx]; }
const_reference operator [] ( size_type idx ) const { return data[idx]; }
reverse_iterator rbegin() { return reverse_iterator( end() ); }
reverse_iterator rend() { return reverse_iterator( begin() ); }
const_reverse_iterator rbegin() const { return const_reverse_iterator( end() ); }
const_reverse_iterator rend() const { return const_reverse_iterator( begin() ); }
const_reverse_iterator crbegin() const { return const_reverse_iterator( end() ); }
const_reverse_iterator crend() const { return const_reverse_iterator( begin() ); }
iterator begin() { return data; }
iterator end() { return data + SIZE_; }
const_iterator begin() const { return data; }
const_iterator end() const { return data + SIZE_; }
const_iterator cbegin() const { return data; }
const_iterator cend() const { return data + SIZE_; }
};
In this template our prof defined a bunch of typedefs, and a few of them had both a non-constant and constant version. First, I wonder what the purpose of each of the public members are, and second, I wonder what the purpose of the constant versions are, in the context of defining a data structure template.
I also notice that some of the member functions are defined as 'const' and I wonder what the purpose of that is.
I wonder what the purpose of each of the public members are.
The class contains the following:
Type aliases: Type information that is exposed from the class for public use.
The non-const typedefs are type aliases for a non-const T.
The const typedefs are type aliases for a const T.
These typedefs are useful because the user of the class might need more information about the type. By using the typedef exposed by the class, he can obtain this. Here's a good example - Consider the situation where we have a template type and we need to access the underlying iterator type. By using the exposed type alias, we can access it:
template<class Container>
void example(Container& c)
{
typename Container::iterator begin = c.begin(),
// ^^^^^^^^^^^^^^^^^^^
end = c.end();
}
I wonder what the purpose of the constant versions are.
Sometimes the user needs to use const types. Therefore using the type information that class provides makes this simple.
I also notice that some of the member functions are defined as 'const' and I wonder what the purpose of that is.
const member functions are functions that do not modify any of the classes data members.
all the typedefs aim to facilitate the use of these types,
std::size_t std::ptrdiff_t is just unsigned int typedefs.
chech here for reverse_iterator
const varialbe defined for the return value of the const version function, which is provided for the const obj, check here
member function defined as const make sure the member variable is not changed in the function body, check here
For this, I think you should try to use this template, and you may really know why it need such public members.
while we are using this template to operate on some data structure, if we want to modify some data, we need a non-constant version. On the other side, if we want to change the value of data, we need a constant version which can help us protect data.
Constant functions can help you, while you are trying to operate on some data and you do not want to modify the member data.
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&).