What should I do to make my container work with ranges? - c++

I have a simple container:
template <class T, class Allocator = std::allocator<T>>
class ring
{
public:
using value_type = T;
using allocator_type = Allocator;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = T &;
using const_reference = const T &;
using pointer = T *;
using const_pointer = const T *;
private:
template <class E>
class ring_iterator
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = E;
using difference_type = std::ptrdiff_t;
using reference = E &;
using pointer = E *;
ring_iterator(const ring_iterator& other) = default;
ring_iterator(ring_iterator&& other) = default;
ring_iterator& operator = (const ring_iterator& other);
pointer operator-> () const;
reference operator* () const;
ring_iterator& operator++ ();
ring_iterator operator++ (int);
ring_iterator& operator-- ();
ring_iterator operator-- (int);
ring_iterator& operator += (difference_type diff);
ring_iterator& operator -= (difference_type diff);
ring_iterator operator + (difference_type diff) const;
ring_iterator operator - (difference_type diff) const;
difference_type operator - (const ring_iterator& other) const;
bool operator == (const ring_iterator& other) const;
bool operator != (const ring_iterator& other) const;
bool operator < (const ring_iterator& other) const;
operator ring_iterator<const E>() const;
};
public:
using iterator = ring_iterator<T>;
using const_iterator = ring_iterator<const T>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
ring(Allocator alloc = {});
ring(size_type cap, Allocator alloc = {});
ring(const ring& other);
ring(ring&& other);
ring& operator = (const ring& other);
ring& operator = (ring&& other);
~ring();
reference front();
reference back();
const_reference front() const;
const_reference back() const;
void push_front(const value_type& val);
void push_front(value_type&& val);
void push_back(const value_type& val);
void push_back(value_type&& val);
void pop_front();
void pop_back();
void reserve(size_t);
void clear();
size_type size() const;
size_type capacity() const;
bool empty() const;
bool full() const;
reference operator[](size_type index);
const_reference operator[](size_type index) const;
reference at(size_type index);
const_reference at(size_type index) const;
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
const_reverse_iterator crbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;
const_reverse_iterator crend() const;
};
What should I do to make the code below compile?
#include <vector>
#include <ranges>
//std::vector<int> v; //compiles
ring<int> v; //does not compile
auto range = v | std::views::transform([](int n) { return n * n; });
MSVC compiler error:
error C2678: binary '|': no operator found which takes a left-hand operand of type 'ring<int,std::allocator<int>>' (or there is no acceptable conversion)

So... after a lot of investigation:
Your iterator must have a public default constructor.
What should I do to make my container work with ranges?
It should satisfy the concept std::ranges::range:
static_assert(std::ranges::range<ring<int>>);
but it doesn't and the error messages are not helpful. So we look at the concept itself:
template< class T >
concept range = requires(T& t) {
ranges::begin(t); // equality-preserving for forward iterators
ranges::end (t);
};
ranges::begin(v) is well defined, but ranges::end(v) gives error "error: no match for call to '(const std::ranges::__cust_access::_End) (ring&)'" with, again, no helpful error messages.
So now we look over std::ranges::end:
template< class T >
requires /* see below */
constexpr std::sentinel_for<ranges::iterator_t<T>> auto end(T&& t);
The documentation here is a bit iffy, but the the failed requirement here is:
static_assert(
std::sentinel_for<ring<int>::iterator, ring<int>::iterator>
);
i.e. the end iterator should be a sentinel for the begin iterator.
It is here where we reach at our first useful error message:
error: static assertion failed
89 | std::sentinel_for<ring<int>::iterator, ring<int>::iterator>
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
note: constraints not satisfied
[...]
opt/compiler-explorer/gcc-trunk-20210906/include/c++/12.0.0/concepts:137:30:
note: the expression is_constructible_v<_Tp, _Args ...> [with _Tp = ring<int, std::allocator<int> >::ring_iterator<int>; _Args = {}]
evaluated to 'false'
137 | = destructible<_Tp> && is_constructible_v<_Tp, _Args...>;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
So there you have it ring<int>::ring_iterator<int> must have a publicly available default constructor.

What should I do to make my container work with ranges?
You should design the container to conform to the "Range" concept.
In short, the container should to provide member functions begin and end which should return iterator and a sentinel. The end sentinel must be reachable from begin iterator. The sentinel type may be the same as the iterator. The iterator type must conform to the "Iterator" concept.
What should I do to make the code below compile?
ring_iterator in your attempt isn't default initialisable, and as such it isn't an Iterator and thus the container isn't a range. Add a default constructor to solve the problem.

Related

Why is my iterator not std::input_iterator?

Why doesn't the iterator below satisfy std::input_iterator concept? What did I miss?
template <class T>
struct IteratorSentinel {};
template <class T>
class Iterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
Iterator() = default;
Iterator(const Iterator&) = delete;
Iterator& operator = (const Iterator&) = delete;
Iterator(Iterator&& other) = default;
Iterator& operator = (Iterator&& other) = default;
T* operator-> ();
T& operator* ();
bool operator== (const IteratorSentinel<T>&) const noexcept;
Iterator& operator++ ();
void operator++ (int);
};
The following static assertion fails:
static_assert(std::input_iterator<Iterator<int>>);
And, for example, the code below does not compile:
template <class T>
auto make_range()
{
return std::ranges::subrange(Iterator<T>(), IteratorSentinel<T>{});
}
std::ranges::equal(make_range<int>(), std::vector<int>());
Your operator* is not const-qualified.
The std::input_iterator concept (through the std::indirectly_readable concept) requires that * can be applied to both const and non-const lvalues as well as rvalues of the type and that in all cases the same type is returned.

How does vector's begin() and end() work, and how should I write it for my custom vector?

I am currently studying Bjarne's PPP, and I don't really understand how begin() and end() works, and how I am supposed to implement it.
Here is my code for the vector.
template<typename T> class vector {
int sz;
T *elem;
int space;
std::allocator<T> alloc;
public:
vector():sz{0},elem{nullptr},space{0}{}
explicit vector(int s):sz{s},elem{new T[s]}, space{s}{
for(int i = 0; i<sz; i++) elem[i]=0;
}
T &at(int n);
const T &at(int n) const;
void reserve(int newalloc);
void resize(int newsize, T def=T());
int capacity() const { return space;}
void push_back(int d);
vector &operator=(const vector &a);
T *begin();
const T *begin() const;
T *end();
const T *end() const;
unsigned long size();
};
begin() and end() should return an iterator. Basically, iterator is a class that implements variables such as iterator_category and value_type, which let other functions to know how to use it.
It should also implement functions required by the iterator_category that you choose to use.
For your a vector, you need a random access iterator. You need two classes: constant access iterator (returned by const function) and non-constant access iterator. Usually you would derive the non-constant class from the constant class.
The positions pointed by the iterators: begin points to the beginning of the data, and end is begin() + size().
template<typename T>
class vector_iterator {
public:
typedef std::random_access_iterator_tag iterator_category;
typedef T value_type;
typedef std::ptrdiff_t difference_type;
typedef const value_type& reference;
typedef const value_type* pointer;
// Functions come here
// You must implement some operators
reference operator*() const { /* must implement */ }
vector_iterator operator+(const difference_type) const { /* must implement */ }
// Some operators can use the operations that are already implemented
vector_iterator operator-(const difference_type n) const { return *this + (-n); }
reference operator[](const difference_type n) const { return *(*this + n); }
};

C++ Custom Iterator

I have two classes that implement a data structure similar to a linked list. We'll call them Node and List. To trim the fat of all the constructors and other methods, they essentially look like this:
class Node {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = Node;
using pointer = Node*;
using reference = Node&;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
// Accessor functions
bool operator!= (value_type const& n) const;
bool operator== (value_type const& n) const;
reference operator*();
pointer operator&();
pointer operator->();
value_type const& operator++() const;
};
class List {
private:
typedef Node* iterator;
typedef const Node* const_iterator;
public:
// Iterators
iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;
// Operator overloads
Node& operator*() const;
const List* operator&() const;
const List* operator->() const;
};
I am trying to achieve something to the effect of:
for (Node n : List)
{
// Some stuff
}
Yet I keep getting the (Visual Studio) error this range-based 'for' statement requires a suitable "begin" function and none was found
What am I missing?

What is the complete interface of a C++ standard compatible library container?

I'm trying to understand the C++ standard library. If I want to build a new kind of container fully compatible with latest C++17 standards (excluding polymorphic allocator support as I'm not that clear on that yet), with full compatibility with iterators, what is the complete set of member functions and types I must provide?
Take a sequence container like std::vector for example. In other words, say I want to provide everything that std::vector provides. Also considering allocator support.
Of course I also want to provide iterators, exactly as std::vector does it (I don't want to delegate to it; I want from the ground up remake), so I suppose I will need two nested iterator classes, one for iterator and one for const_iterator.
I've come up with the following:
template <class T, class Alloc = std::allocator_traits<T>>
class StdLibClass
{
public:
using allocator_type = Alloc;
using value_type = typename Alloc::value_type;
using reference = typename Alloc::reference;
using const_reference = typename Alloc::const_reference;
using difference_type = typename Alloc::difference_type;
using size_type = typename Alloc::size_type;
class Iterator
{
public:
using difference_type = typename Alloc::difference_type;
using value_type = typename Alloc::value_type;
using reference = typename Alloc::reference;
using pointer = typename Alloc::pointer;
using iterator_category = std::random_access_iterator_tag; // or another Iterator tag you want
Iterator();
Iterator(const Iterator&);
~Iterator();
Iterator& operator=(const Iterator&);
bool operator==(const Iterator&) const;
bool operator!=(const Iterator&) const;
bool operator<(const Iterator&) const;
bool operator>(const Iterator&) const;
bool operator<=(const Iterator&) const;
bool operator>=(const Iterator&) const;
Iterator &operator++();
Iterator operator++(int);
Iterator &operator--();
Iterator operator--(int);
Iterator &operator+=(size_type);
Iterator operator+(size_type) const;
friend Iterator operator+(size_type, const Iterator&);
Iterator &operator-=(size_type);
Iterator operator-(size_type) const;
difference_type operator-(Iterator) const;
reference operator*() const;
pointer operator->() const;
reference operator[](size_type) const;
};
class ConstIterator
{
public:
using difference_type = typename Alloc::difference_type;
using value_type = typename Alloc::value_type;
using reference = typename const Alloc::reference;
using pointer = typename const Alloc::pointer;
using iterator_category = std::random_access_iterator_tag; // or another Iterator tag you want
ConstIterator();
ConstIterator(const ConstIterator&);
ConstIterator(const Iterator&);
~ConstIterator();
ConstIterator& operator=(const ConstIterator&);
bool operator==(const ConstIterator&) const;
bool operator!=(const ConstIterator&) const;
bool operator<(const ConstIterator&) const;
bool operator>(const ConstIterator&) const;
bool operator<=(const ConstIterator&) const;
bool operator>=(const ConstIterator&) const;
ConstIterator &operator++();
ConstIterator operator++(int);
ConstIterator &operator--();
ConstIterator operator--(int);
ConstIterator &operator+=(size_type);
ConstIterator operator+(size_type) const;
friend ConstIterator operator+(size_type, const ConstIterator&);
ConstIterator &operator-=(size_type);
ConstIterator operator-(size_type) const;
difference_type operator-(ConstIterator) const;
reference operator*() const;
pointer operator->() const;
reference operator[](size_type) const;
};
using ReverseIterator = std::reverse_iterator<Iterator>;
using ConstReverseIterator = std::const_reverse_iterator<ConstIterator>;
StdLibClass();
~StdLibClass();
StdLibClass(const StdLibClass&);
StdLibClass& operator=(const StdLibClass&);
StdLibClass(const StdLibClass&&);
StdLibClass& operator=(const StdLibClass&&);
bool operator==(const StdLibClass&) const;
bool operator!=(const StdLibClass&) const;
bool operator<(const StdLibClass&) const;
bool operator>(const StdLibClass&) const;
bool operator<=(const StdLibClass&) const;
bool operator>=(const StdLibClass&) const;
Iterator begin();
ConstIterator begin() const;
ConstIterator cbegin() const;
Iterator end();
ConstIterator end() const;
ConstIterator cend() const;
ReverseIterator rbegin();
ConstReverseIterator rbegin() const;
ConstReverseIterator crbegin() const;
ReverseIterator rend();
ConstReverseIterator rend() const;
ConstReverseIterator crend() const;
reference front();
const_reference front() const;
reference back();
const_reference back() const;
template <class... TArgs>
void emplace_front(TArgs &&...);
template <class... TArgs>
void emplace_back(TArgs &&...);
void push_front(const T&);
void push_front(T&&);
void push_back(const T&);
void push_back(T&&);
void pop_front();
void pop_back();
reference operator[](size_type);
const_reference operator[](size_type) const;
reference at(size_type);
const_reference at(size_type) const;
template <class... TArgs>
Iterator emplace(ConstIterator, TArgs&&...);
Iterator insert(ConstIterator, const T&);
Iterator insert(ConstIterator, T&&);
Iterator insert(ConstIterator, size_type, T&);
template <class Iter>
Iterator insert(ConstIterator, Iter, Iter);
Iterator insert(ConstIterator, std::initializer_list<T>);
Iterator erase(ConstIterator);
Iterator erase(ConstIterator, ConstIterator);
void clear();
template <class Iter>
void assign(Iter, Iter);
void assign(std::initializer_list<T>);
void assign(size_type, const T&);
void swap(StdLibClass &);
size_type size() const;
size_type max_size() const;
bool empty() const;
Alloc get_allocator() const;
};
// possibly want to specialize std::swap for the class too
namespace std
{
template <>
void swap<T, StdLibClass<T, Alloc>>(StdLibClass<T, Alloc>&, StdLibClass<T, Alloc>&);
} // namespace std
int main()
{
return 0;
}
Some I've found in webpages around the internet, others I've found by digging the standard library headers. Is there some member or some other concept I'm missing?
I think, depending on the kind of container you want to implement, the new standard requirements (standard concepts) added in C++20 are a good place to look for the required interface of a standard container.
Example for Container and especially AllocatorAwareContainer

How to correctly implement custom iterators and const_iterators?

I have a custom container class for which I'd like to write the iterator and const_iterator classes.
I never did this before and I failed to find an appropriate how-to. What are the guidelines regarding iterator creation, and what should I be aware of ?
I'd also like to avoid code duplication (I feel that const_iterator and iterator share many things; should one subclass the other ?).
Foot note: I'm pretty sure Boost has something to ease this but I can't use it here, for many stupid reasons.
Choose type of iterator which fits your container: input, output, forward etc.
Use base iterator classes from standard library. For example, std::iterator with random_access_iterator_tag.These base classes define all type definitions required by STL and do other work.
To avoid code duplication iterator class should be a template class and be parametrized by "value type", "pointer type", "reference type" or all of them (depends on implementation). For example:
// iterator class is parametrized by pointer type
template <typename PointerType> class MyIterator {
// iterator class definition goes here
};
typedef MyIterator<int*> iterator_type;
typedef MyIterator<const int*> const_iterator_type;
Notice iterator_type and const_iterator_type type definitions: they are types for your non-const and const iterators.
See Also: standard library reference
EDIT: std::iterator is deprecated since C++17. See a relating discussion here.
I'm going to show you how you can easily define iterators for your custom containers, but just in case I have created a c++11 library that allows you to easily create custom iterators with custom behavior for any type of container, contiguous or non-contiguous.
You can find it on Github
Here are the simple steps to creating and using custom iterators:
Create your "custom iterator" class.
Define typedefs in your "custom container" class.
e.g. typedef blRawIterator< Type > iterator;
e.g. typedef blRawIterator< const Type > const_iterator;
Define "begin" and "end" functions
e.g. iterator begin(){return iterator(&m_data[0]);};
e.g. const_iterator cbegin()const{return const_iterator(&m_data[0]);};
We're Done!!!
Finally, onto defining our custom iterator classes:
NOTE: When defining custom iterators, we derive from the standard iterator categories to let STL algorithms know the type of iterator we've made.
In this example, I define a random access iterator and a reverse random access iterator:
//-------------------------------------------------------------------
// Raw iterator with random access
//-------------------------------------------------------------------
template<typename blDataType>
class blRawIterator
{
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = blDataType;
using difference_type = std::ptrdiff_t;
using pointer = blDataType*;
using reference = blDataType&;
public:
blRawIterator(blDataType* ptr = nullptr){m_ptr = ptr;}
blRawIterator(const blRawIterator<blDataType>& rawIterator) = default;
~blRawIterator(){}
blRawIterator<blDataType>& operator=(const blRawIterator<blDataType>& rawIterator) = default;
blRawIterator<blDataType>& operator=(blDataType* ptr){m_ptr = ptr;return (*this);}
operator bool()const
{
if(m_ptr)
return true;
else
return false;
}
bool operator==(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr == rawIterator.getConstPtr());}
bool operator!=(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr != rawIterator.getConstPtr());}
blRawIterator<blDataType>& operator+=(const difference_type& movement){m_ptr += movement;return (*this);}
blRawIterator<blDataType>& operator-=(const difference_type& movement){m_ptr -= movement;return (*this);}
blRawIterator<blDataType>& operator++(){++m_ptr;return (*this);}
blRawIterator<blDataType>& operator--(){--m_ptr;return (*this);}
blRawIterator<blDataType> operator++(int){auto temp(*this);++m_ptr;return temp;}
blRawIterator<blDataType> operator--(int){auto temp(*this);--m_ptr;return temp;}
blRawIterator<blDataType> operator+(const difference_type& movement){auto oldPtr = m_ptr;m_ptr+=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
blRawIterator<blDataType> operator-(const difference_type& movement){auto oldPtr = m_ptr;m_ptr-=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
difference_type operator-(const blRawIterator<blDataType>& rawIterator){return std::distance(rawIterator.getPtr(),this->getPtr());}
blDataType& operator*(){return *m_ptr;}
const blDataType& operator*()const{return *m_ptr;}
blDataType* operator->(){return m_ptr;}
blDataType* getPtr()const{return m_ptr;}
const blDataType* getConstPtr()const{return m_ptr;}
protected:
blDataType* m_ptr;
};
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Raw reverse iterator with random access
//-------------------------------------------------------------------
template<typename blDataType>
class blRawReverseIterator : public blRawIterator<blDataType>
{
public:
blRawReverseIterator(blDataType* ptr = nullptr):blRawIterator<blDataType>(ptr){}
blRawReverseIterator(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();}
blRawReverseIterator(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
~blRawReverseIterator(){}
blRawReverseIterator<blDataType>& operator=(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
blRawReverseIterator<blDataType>& operator=(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();return (*this);}
blRawReverseIterator<blDataType>& operator=(blDataType* ptr){this->setPtr(ptr);return (*this);}
blRawReverseIterator<blDataType>& operator+=(const difference_type& movement){this->m_ptr -= movement;return (*this);}
blRawReverseIterator<blDataType>& operator-=(const difference_type& movement){this->m_ptr += movement;return (*this);}
blRawReverseIterator<blDataType>& operator++(){--this->m_ptr;return (*this);}
blRawReverseIterator<blDataType>& operator--(){++this->m_ptr;return (*this);}
blRawReverseIterator<blDataType> operator++(int){auto temp(*this);--this->m_ptr;return temp;}
blRawReverseIterator<blDataType> operator--(int){auto temp(*this);++this->m_ptr;return temp;}
blRawReverseIterator<blDataType> operator+(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr-=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
blRawReverseIterator<blDataType> operator-(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr+=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
difference_type operator-(const blRawReverseIterator<blDataType>& rawReverseIterator){return std::distance(this->getPtr(),rawReverseIterator.getPtr());}
blRawIterator<blDataType> base(){blRawIterator<blDataType> forwardIterator(this->m_ptr); ++forwardIterator; return forwardIterator;}
};
//-------------------------------------------------------------------
Now somewhere in your custom container class:
template<typename blDataType>
class blCustomContainer
{
public: // The typedefs
typedef blRawIterator<blDataType> iterator;
typedef blRawIterator<const blDataType> const_iterator;
typedef blRawReverseIterator<blDataType> reverse_iterator;
typedef blRawReverseIterator<const blDataType> const_reverse_iterator;
.
.
.
public: // The begin/end functions
iterator begin(){return iterator(&m_data[0]);}
iterator end(){return iterator(&m_data[m_size]);}
const_iterator cbegin(){return const_iterator(&m_data[0]);}
const_iterator cend(){return const_iterator(&m_data[m_size]);}
reverse_iterator rbegin(){return reverse_iterator(&m_data[m_size - 1]);}
reverse_iterator rend(){return reverse_iterator(&m_data[-1]);}
const_reverse_iterator crbegin(){return const_reverse_iterator(&m_data[m_size - 1]);}
const_reverse_iterator crend(){return const_reverse_iterator(&m_data[-1]);}
.
.
.
// This is the pointer to the
// beginning of the data
// This allows the container
// to either "view" data owned
// by other containers or to
// own its own data
// You would implement a "create"
// method for owning the data
// and a "wrap" method for viewing
// data owned by other containers
blDataType* m_data;
};
They often forget that iterator must convert to const_iterator but not the other way around. Here is a way to do that:
template<class T, class Tag = void>
class IntrusiveSlistIterator
: public std::iterator<std::forward_iterator_tag, T>
{
typedef SlistNode<Tag> Node;
Node* node_;
public:
IntrusiveSlistIterator(Node* node);
T& operator*() const;
T* operator->() const;
IntrusiveSlistIterator& operator++();
IntrusiveSlistIterator operator++(int);
friend bool operator==(IntrusiveSlistIterator a, IntrusiveSlistIterator b);
friend bool operator!=(IntrusiveSlistIterator a, IntrusiveSlistIterator b);
// one way conversion: iterator -> const_iterator
operator IntrusiveSlistIterator<T const, Tag>() const;
};
In the above notice how IntrusiveSlistIterator<T> converts to IntrusiveSlistIterator<T const>. If T is already const this conversion never gets used.
Boost has something to help: the Boost.Iterator library.
More precisely this page: boost::iterator_adaptor.
What's very interesting is the Tutorial Example which shows a complete implementation, from scratch, for a custom type.
template <class Value>
class node_iter
: public boost::iterator_adaptor<
node_iter<Value> // Derived
, Value* // Base
, boost::use_default // Value
, boost::forward_traversal_tag // CategoryOrTraversal
>
{
private:
struct enabler {}; // a private type avoids misuse
public:
node_iter()
: node_iter::iterator_adaptor_(0) {}
explicit node_iter(Value* p)
: node_iter::iterator_adaptor_(p) {}
// iterator convertible to const_iterator, not vice-versa
template <class OtherValue>
node_iter(
node_iter<OtherValue> const& other
, typename boost::enable_if<
boost::is_convertible<OtherValue*,Value*>
, enabler
>::type = enabler()
)
: node_iter::iterator_adaptor_(other.base()) {}
private:
friend class boost::iterator_core_access;
void increment() { this->base_reference() = this->base()->next(); }
};
The main point, as has been cited already, is to use a single template implementation and typedef it.
I don't know if Boost has anything that would help.
My preferred pattern is simple: take a template argument which is equal to value_type, either const qualified or not. If necessary, also a node type. Then, well, everything kind of falls into place.
Just remember to parameterize (template-ize) everything that needs to be, including the copy constructor and operator==. For the most part, the semantics of const will create correct behavior.
template< class ValueType, class NodeType >
struct my_iterator
: std::iterator< std::bidirectional_iterator_tag, T > {
ValueType &operator*() { return cur->payload; }
template< class VT2, class NT2 >
friend bool operator==
( my_iterator const &lhs, my_iterator< VT2, NT2 > const &rhs );
// etc.
private:
NodeType *cur;
friend class my_container;
my_iterator( NodeType * ); // private constructor for begin, end
};
typedef my_iterator< T, my_node< T > > iterator;
typedef my_iterator< T const, my_node< T > const > const_iterator;
There are plenty of good answers but I created a template header I use that is quite concise and easy to use.
To add an iterator to your class it is only necessary to write a small class to represent the state of the iterator with 7 small functions, of which 2 are optional:
#include <iostream>
#include <vector>
#include "iterator_tpl.h"
struct myClass {
std::vector<float> vec;
// Add some sane typedefs for STL compliance:
STL_TYPEDEFS(float);
struct it_state {
int pos;
inline void begin(const myClass* ref) { pos = 0; }
inline void next(const myClass* ref) { ++pos; }
inline void end(const myClass* ref) { pos = ref->vec.size(); }
inline float& get(myClass* ref) { return ref->vec[pos]; }
inline bool equals(const it_state& s) const { return pos == s.pos; }
// Optional to allow operator--() and reverse iterators:
inline void prev(const myClass* ref) { --pos; }
// Optional to allow `const_iterator`:
inline const float& get(const myClass* ref) const { return ref->vec[pos]; }
};
// Declare typedef ... iterator;, begin() and end() functions:
SETUP_ITERATORS(myClass, float&, it_state);
// Declare typedef ... reverse_iterator;, rbegin() and rend() functions:
SETUP_REVERSE_ITERATORS(myClass, float&, it_state);
};
Then you can use it as you would expect from an STL iterator:
int main() {
myClass c1;
c1.vec.push_back(1.0);
c1.vec.push_back(2.0);
c1.vec.push_back(3.0);
std::cout << "iterator:" << std::endl;
for (float& val : c1) {
std::cout << val << " "; // 1.0 2.0 3.0
}
std::cout << "reverse iterator:" << std::endl;
for (auto it = c1.rbegin(); it != c1.rend(); ++it) {
std::cout << *it << " "; // 3.0 2.0 1.0
}
}
I hope it helps.
I came across this post and was surprised that a simple method is not really mentioned here. Using a pointer to the value like how std::iterator describes is obviously a very generic approach. But you might be able to get away with something much simpler. Of course this is a simplistic approach and might not always be sufficient, but in case it is, I am posting it for the next reader.
Most probably the underlying type in your class is an STL container which already has defined the iterators for you. If that is the case, you can simply use their defined iterators and don't really need to make your own.
Here is an example:
class Foo {
std::vector<int>::iterator begin() { return data.begin(); }
std::vector<int>::iterator end() { return data.end(); }
std::vector<int>::const_iterator begin() const { return data.begin(); }
std::vector<int>::const_iterator end() const { return data.end(); }
private:
std::vector<int> data
};
i'm interested to know how correct this is, but seems to work as a roll-your-own iterator to internal data storage
template<typename T>
struct iterator_type
{
using self_type = iterator_type;
using iterator_category = std::random_access_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = std::remove_cv_t<T>;
using pointer = T*;
using reference = T&;
iterator_type( pointer ptr ) noexcept
: _ptr{ ptr }
{}
reference operator*() noexcept { return *_ptr; }
pointer operator->() noexcept { return _ptr; }
self_type operator++() noexcept { ++_ptr; return *this; }
self_type operator++(int) noexcept { self_type tmp = *this; ++_ptr; return tmp; }
self_type operator--() noexcept { --_ptr; return *this; }
self_type operator--(int) noexcept { self_type tmp = *this; --_ptr; return tmp; }
bool operator==( const self_type &other ) const noexcept { return _ptr == other._ptr; }
bool operator!=( const self_type &other ) const noexcept { return _ptr != other._ptr; }
private:
pointer _ptr;
};
template<typename T>
using const_iterator_type = iterator_type<std::add_const_t<T>>;
Then i just add these to my class, and seems to work as expected.
template<typename T>
class Container
{
public:
using iterator = iterator_type<T>;
using const_iterator = const_iterator_type<T>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
...
iterator begin() { return _begin; }
iterator end() { return _begin + _size; }
const_iterator cbegin() const { return _begin; }
const_iterator cend() const { return _begin + _size; }
reverse_iterator rbegin() { return reverse_iterator(_begin + _size); }
reverse_iterator rend() { return reverse_iterator(_begin); }
const_reverse_iterator crbegin() const { return const_reverse_iterator(_begin + _size); }
const_reverse_iterator crend() const { return const_reverse_iterator(_begin); }
private:
T* _begin;
size_t _size;
size_t _capacity;
};
the only thing is that to make it with the std::cbegin(), std::rcbegin(), std::cend() and std::rcend() functions I have to extend the std namespace:
namespace std
{
template<typename T>
typename Container<T>::const_iterator cbegin( Container<T> &c ) { return c.cbegin(); }
template<typename T>
typename Container<T>::const_iterator cend( Container<T> &c ) { return c.cend(); }
template<typename T>
typename Container<T>::const_reverse_iterator crbegin( Container<T> &c ) { return c.crbegin(); }
template<typename T>
typename Container<T>::const_reverse_iterator crend( Container<T> &c ) { return c.crend(); }
}
Check this below code, it works
#define MAX_BYTE_RANGE 255
template <typename T>
class string
{
public:
typedef char *pointer;
typedef const char *const_pointer;
typedef __gnu_cxx::__normal_iterator<pointer, string> iterator;
typedef __gnu_cxx::__normal_iterator<const_pointer, string> const_iterator;
string() : length(0)
{
}
size_t size() const
{
return length;
}
void operator=(const_pointer value)
{
if (value == nullptr)
throw std::invalid_argument("value cannot be null");
auto count = strlen(value);
if (count > 0)
_M_copy(value, count);
}
void operator=(const string &value)
{
if (value.length != 0)
_M_copy(value.buf, value.length);
}
iterator begin()
{
return iterator(buf);
}
iterator end()
{
return iterator(buf + length);
}
const_iterator begin() const
{
return const_iterator(buf);
}
const_iterator end() const
{
return const_iterator(buf + length);
}
const_pointer c_str() const
{
return buf;
}
~string()
{
}
private:
unsigned char length;
T buf[MAX_BYTE_RANGE];
void _M_copy(const_pointer value, size_t count)
{
memcpy(buf, value, count);
length = count;
}
};