Iterators for circular structures

后端 未结 2 2014
一个人的身影
一个人的身影 2021-01-06 16:52

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

相关标签:
2条回答
  • 2021-01-06 17:22

    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:

    1. 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.

    2. 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: the iterator originated as a result of end;
      • Inc: the iterator did not originate from end and has been most recently incremented;
      • Default: otherwise.

      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:

    1. You're not incrementing starting at end, since that's illegal.
    2. Only after an increment you might be before the end, or at the end.

    Thus, the iterators are identical when their pointers are identical, and:

    • the other iterator is not tagged End, or
    • this iterator is not tagged Default (it must be itself End or Inc - recently incremented).

    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
    
    0 讨论(0)
  • 2021-01-06 17:47

    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;
    
    0 讨论(0)
提交回复
热议问题