The following code shows what I currently have. It is an adapter for circular data-structures. The main function shows how it is used. This is all nice and fast but I really
Usually, "circulator" means a circular iteration adapter for a linear structure. What you desire is really an inverse adapter: it takes a circular structure, and presents a linear iterator that has a beginning and an end - such concepts simply don't apply to circular iterators or circular structure.
So, to avoid confusion, I'll refer to the iterator as a circ_iterator
. The true circulator
for your circular structure is trivial, and must not care about any ends or beginnings.
The desired functionality can be had by tagging the iterator:
The idiomatic way of getting start
/end
iterators for type T
is via begin
and end
in the namespace where T
lives, or via member functions of the same name. The instantiation circ_iterator end{a}
would be non-idiomatic. Instead, overload begin
and end
on circ&
. Both return an iterator pointing to the argument. begin
tags the iterator Default
, and end
tags the iterator End
. See this question for details.
Only the end iterator is special, and all of the typical iterator semantics can be had by adding a small, three-valued tag to the iterator. Its values are:
end
;end
and has been most recently incremented;An iterator obtained from end
will retain its End
tag forever. Otherwise, an iterator starts with the Default
tag, and switches to Inc
on increment, and back to Default
on decrement.
Note that begin
and end
can never be the same, since there's no way for the circular container to have a zero size: a circ
item always holds at least one data item. You could, of course represent an absence of a circ
instance using a null iterator that compares equal to any other null iterator.
The increment operation is special, since the only legal way to approach the end iterator is by incrementing. When doing so, the following must hold:
Thus, the iterators are identical when their pointers are identical, and:
Since the tag is small (2 bits wide), you can assume, or statically assert, that the circ
type is aligned to 4 bytes and the platform-specific uintptr_t
<->*circ
conversions are "sane", and using the tagged pointer trick to retain the tag in the least significant bits of the pointer. I provide both the version that employs the tagged pointer trick, and one that doesn't.
Finally, it's much easier to implement iterators by deriving from boost::iterator_facade
. I'm leaving the implementation of const_circ_iterator
as an exercise to the reader. It is well documented.
The code compiles on MSVC2012 and also on LLVM 6.
First, let's deal with the tagged pointers - this is a very basic implementation, but will do for our purposes.
// https://github.com/KubaO/stackoverflown/tree/master/questions/circ-
iterator-9993713
#include <boost/iterator/iterator_facade.hpp>
#include <boost/noncopyable.hpp>
#include <boost/operators.hpp>
#include <limits>
#include <iostream>
#include <cassert>
#include <cstdint>
#include <algorithm>
template <typename T, bool merge_tag = false, typename tag_type = uint8_t> class tagged_ptr;
template <typename T, typename tag_type> class tagged_ptr<T, true, tag_type> {
uintptr_t ptr;
typedef std::numeric_limits<uintptr_t> lim;
inline static uintptr_t ptr_of(T* p) {
assert(tag_of(p) == 0);
return uintptr_t(p);
}
inline static uintptr_t tag_mask() { return 3; }
inline uintptr_t ptr_only() const { return ptr & (lim::max() - tag_mask()); }
inline static tag_type tag_of(T* p) { return ((tag_type)(uintptr_t)p) & tag_mask(); }
inline tag_type tag_only() const { return ptr & tag_mask(); }
public:
tagged_ptr(T* p, tag_type t) : ptr(ptr_of(p) | t) { assert(t <= tag_mask()); }
tagged_ptr(const tagged_ptr & other) : ptr(other.ptr) {}
operator T*() const { return reinterpret_cast<T*>(ptr_only()); }
T* operator->() const { return reinterpret_cast<T*>(ptr_only()); }
tagged_ptr & operator=(T* p) { ptr = tag_only() | ptr_of(p); return *this; }
tag_type tag() const { return tag_only(); }
void set_tag(tag_type tag) { assert(tag <= tag_mask()); ptr = tag | ptr_only(); }
};
template <typename T, typename tag_type> class tagged_ptr<T, false, tag_type> {
T* ptr;
tag_type m_tag;
public:
tagged_ptr(T* p, tag_type t) : ptr(p), m_tag(t) {}
tagged_ptr(const tagged_ptr & other) : ptr(other.ptr), m_tag(other.m_tag) {}
operator T*() const { return ptr; }
T* operator->() const { return ptr; }
tagged_ptr & operator=(T* p) { ptr = p; return *this; }
tag_type tag() const { return m_tag; }
void set_tag(tag_type tag) { m_tag = tag; }
};
The circ
class can have some convenience constructors to make constructing circular lists easier, and to avoid the mistake you made in your question (a.prev = &a
is wrong).
struct circ : private boost::noncopyable {
unsigned int i;
circ* next;
circ* prev;
explicit circ(int i) : i(i), next(nullptr), prev(nullptr) {}
circ(int i, circ& prev) : i(i), next(nullptr), prev(&prev) {
prev.next = this;
}
circ(int i, circ& prev, circ& next) : i(i), next(&next), prev(&prev) {
prev.next = this;
next.prev = this;
}
};
The circ_iterator is then:
class circ_iterator;
circ_iterator end(circ& c);
class circ_iterator
: public boost::iterator_facade<
circ_iterator, circ, boost::bidirectional_traversal_tag
>
{
tagged_ptr<circ> c;
enum { Default, Inc, End };
friend class boost::iterator_core_access;
friend circ_iterator end(circ&);
struct end {};
circ_iterator(circ& c_, end) : c(&c_, End) {}
circ& dereference() const { return *c; }
void increment() {
c = c->next;
if (c.tag() != End) c.set_tag(Inc);
}
void decrement() {
c = c->prev;
if (c.tag() != End) c.set_tag(Default);
}
bool equal(const circ_iterator & other) const {
return this->c == other.c &&
(other.c.tag() != End || this->c.tag() != Default);
}
public:
circ_iterator() : c(nullptr, Default) {}
circ_iterator(circ& c_) : c(&c_, Default) {}
circ_iterator(const circ_iterator& other) : c(other.c) {}
};
circ_iterator begin(circ& c) { return circ_iterator(c); }
circ_iterator end(circ& c) { return circ_iterator(c, circ_iterator::end()); }
Finally, a simple demonstration:
int main()
{
circ a(0), b(1, a), c(2, b), d(3, c, a);
assert(end(a) == end(a));
assert(++--end(a) == end(a));
for (auto it = begin(a); it != end(a); ++it) {
std::cout << it->i << std::endl;
}
std::cout << "*" << std::endl;
for (auto it = ++begin(a); it != --end(a); ++it) {
std::cout << it->i << std::endl;
}
std::cout << "*" << std::endl;
for (auto & c : a)
std::cout << c.i << std::endl;
}
Output:
0
1
2
3
*
1
2
*
0
1
2
3
If it were me, I'd have operator++
notice the terminal condition, and set c
to some sentinal value:
circulator(circ& c) : c(&c), start(&c) {}
circulator& operator++() { c = c->next; if(c==start) c=nullptr; return *this; }
Usage:
circulator begin{a}, end;
while(begin != end) {
begin++;
}
Note that this usage defines the end iterator as holding a nullptr, which means that you can't do this:
circulator end;
--end;