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{});
Related
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
This relates to my previous question here.
Here's a summary of that thread: I am trying to implement a doubly-linked-list class called my_list in C++, including iterators and I want automatic implicit conversion of iterator to const_iterator.
My original thought was to have something like
template<class T>
class my_list_iterator<T>
{
node<T> pos_;
/* code */
operator my_list_iterator<const T>(){return my_list_iterator<const T>(pos_);}
};
and then inside my_list define iterator as my_list_iterator<T> and const_iterator as my_list_iterator<const T>.
However, as pointed out by Miled Budneck in the previous thread, this won't work because a pointer to node<T> cannot convert to a pointer to node<const T>. He suggested I re-implement the const iterator as a pointer to const node instead.
While that works, by looking around I also found one other possible way to define the iterator class:
template<class T, class Pointer, class Reference>
class my_list_iterator {
node<T>* pos_;
/* code */
operator my_list_iterator<T,const Pointer,const Reference>() {return my_list_iterator<T,const Pointer,const Reference>(pos_);}
};
Then I can define my_list<T>::iterator as my_list_iterator<T,T*,T&> and my_list<T>::const_iterator as my_list_iterator<T,const T*,const T&>. I thought the last line would take care of the implicit conversion issue, but it seems to not be used at all.
For reference, here is the complete code:
template<class T> class node {
node(const T& t = T()):data(t),next(0),prev(0) {}
T data;
node* next;
node* prev;
friend class my_list<T>;
template<class U,class Pointer,class Reference> friend class my_list_iterator;
};
template<class T,class Pointer,class Reference> class my_list_iterator {
public:
// increment and decrement operators
my_list_iterator operator++();
my_list_iterator operator++(int);
my_list_iterator operator--();
my_list_iterator operator--(int);
// bool comparison iterators
bool operator==(const my_list_iterator& other) const {return pos_==other.pos_;}
bool operator!=(const my_list_iterator& other) const {return pos_!=other.pos_;}
// member access
Reference operator*() const {return pos_->data;}
Pointer operator->() const {return &(pos_->data);}
// conversion to constant
operator my_list_iterator<T,const Pointer,const Reference>() {return pos_;}
private:
node<T>* pos_;
explicit my_list_iterator(node<T>* p=0):pos_(p) {}
friend class my_list<T>;
};
and the error message in the end
note: no known conversion for argument 1 from ‘my_list<int>::iterator’ {aka ‘my_list_iterator<int, int*, int&>’} to ‘const my_list_iterator<int, const int*, const int&>&’
So why does this code not work? Is there any small modification I can make to make it work, or is this design impossible to implement?
Given Pointer = T*, const Pointer is T* const, not const T*. Type substitution is not like macro expansion. const Pointer adds a top-level const to the type denoted by Pointer. Similarly, const Reference is T& const (which is automatically adjusted to T& because top-level cv-qualifiers on references are ignored) instead of const T&. (See What is the difference between const int*, const int * const, and int const *? for the difference between T* const and const T*.)
You need to use something like const std::remove_pointer<Pointer>* for this to work.
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.
This is a code a got for now but it doesnt compile due to an incomplete variable type container::iterator error. The class and the iterator are still pretty simple since I am trying to figure out how to organise my code.
template <typename T> class container {
using size_type = std::size_t;
using pointer = T*;
using const_pointer = const T*;
public:
class iterator;
container (size_type n=0): data {n>0 ? new T[n]:nullptr}, size {n>0 ? n:0}{};
container (iterator begin, iterator end){};
~container (){ delete[] data; };
private:
pointer data;
size_type size;
};
template <typename T> class container<T>::iterator {
public:
iterator (pointer p): current {p} {};
iterator& operator++ (){ current++; return *this; };
iterator& operator-- (){ current--; return *this; };
reference operator* (){ return *current; };
bool operator== (const iterator& b) const { return current == b.current; };
bool operator!= (const iterator& b) const { return current != b.current; };
private:
pointer current;
};
I would like to keep the iterator definition out of the container class if possible. Thanks for any replies.
container (iterator begin, iterator end){};
That uses iterator but at that point in the code there's no definition of iterator, only a declaration. You'll need to define the structure of your iterator before you can even declare this constructor.
You could alter this constructor to take references. Then you can declare it within your class but then define it below the definition for iterator. IMO this isn't the best way to go, but it's possible.
I would like to keep the iterator definition out of the container class if possible.
It isn't, because the arguments for container's member functions cannot be properly analysed without a definition for their type.
Move it inline.
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.