7
\$\begingroup\$

Following the paradigms of SCARY iterators, there should only be a single iterator-type for contiguous iterators per value-type.
Here my code for that iterator, working with C++11+:

#include <type_traits>
#include <iterator>

template<class T, class U>
struct is_more_cv_qualified : std::integral_constant<bool, std::is_same<T, const volatile U>{}
    || std::is_same<T, const U>{} || std::is_same<T, volatile U>{}>;

struct contiguous_iterator_tag : std::random_access_iterator_tag {};
struct mutable_contiguous_iterator_tag : contiguous_iterator_tag, std::output_iterator_tag {};

template<class T>
struct contiguous_iterator_base {
    constexpr bool operator==(contiguous_iterator_base other) const noexcept { return _p == other.p; }
    constexpr bool operator!=(contiguous_iterator_base other) const noexcept { return _p != other.p; }
    constexpr bool operator<(contiguous_iterator_base other) const noexcept { return _p < other._p; }
    constexpr bool operator>(contiguous_iterator_base other) const noexcept { return _p > other._p; }
    constexpr bool operator<=(contiguous_iterator_base other) const noexcept { return _p <= other._p; }
    constexpr bool operator>=(contiguous_iterator_base other) const noexcept { return _p >= other._p; }
    constexpr std::ptrdiff_t operator-(contiguous_iterator_base other) const noexcept { return _p - other._p; }
protected:
    constexpr contiguous_iterator_base& operator=(const contiguous_iterator_base&) noexcept = default;
    T* _p = nullptr;
};

template<class T>
struct contiguous_iterator : contiguous_iterator_base<std::remove_cv<T>::type>
, std::iterator<typename std::conditional<std::is_const<T>{}, contiguous_iterator_tag, mutable_contiguous_iterator_tag>::type, T> {
    constexpr contiguous_iterator() noexcept = default;
    template<class U, class = std::enable_if<is_more_cv_qualified<T, U>{}>::type>
    constexpr contiguous_iterator(contiguous_iterator<U> other) noexcept : contiguous_iterator_base<std::remove_cv<T>::type>(other) {}
    constexpr explicit contiguous_iterator(contiguous_iterator_base<std::remove_cv<T>::type> other) noexcept : decltype(other)(other) {}
    constexpr explicit contiguous_iterator(T* p) noexcept { _p = const_cast<decltype(_p)>(p); }
    template<class U>
    constexpr contiguous_iterator<U> rebind() const noexcept { return contiguous_iterator<U>(_p); }
    constexpr T* operator->() const noexcept { return  _p; }
    constexpr T& operator* () const noexcept { return *_p; }
    constexpr T& operator[](std::ptrdiff_t d) const noexcept { return _p[d]; }
    constexpr contiguous_iterator& operator++() noexcept { ++_p; return *this; }
    constexpr contiguous_iterator operator++(int) noexcept { auto r = *this; ++*this; return r; }
    constexpr contiguous_iterator& operator--() noexcept { --_p; return *this; }
    constexpr contiguous_iterator operator--(int) noexcept { auto r = *this; --*this; return r; }
    constexpr contiguous_iterator& operator+=(std::ptrdiff_t d) noexcept { _p += d; return *this; }
    constexpr contiguous_iterator& operator-=(std::ptrdiff_t d) noexcept { _p -= d; return *this; }
    constexpr contiguous_iterator operator+(std::ptrdiff_t d) const noexcept { return contiguous_iterator(_p) += d; }
    constexpr contiguous_iterator operator-(std::ptrdiff_t d) const noexcept { return contiguous_iterator(_p) -= d; }
protected:
    using contiguous_iterator_base<std::remove_cv<T>::type>::_p;
};

// smoke test

int main() {
    contiguous_iterator_base<int>{};
    contiguous_iterator<int> a;
    contiguous_iterator<const int> b;
    b = a;
    a = b.rebind<int>();
    //a.rebind<char>(); error
}
\$\endgroup\$
4
  • 2
    \$\begingroup\$ Unrelated, but SCARY has got to be the stupidest acronym ever. \$\endgroup\$
    – Barry
    Commented Dec 14, 2015 at 2:38
  • \$\begingroup\$ At the very least, they are in the running... \$\endgroup\$ Commented Dec 14, 2015 at 2:44
  • \$\begingroup\$ This topic is not very new but I just wrote a somewhat identical wrapper but I have operator->, operator* and operator[] marked const since I'd find it surprising to not be able to dereference a const iterator. \$\endgroup\$ Commented Oct 18, 2016 at 1:29
  • \$\begingroup\$ some random comments in no particular order: 1) There is not much value in deriving from std::iterator (besides it is deprecated). 2) would you consider making the contiguous iterator convertible (explicitly or implicitly) to the pointer, or by means of an idiomatic function, such as .data()/data(It), 3) Some of standard iterators are (accidentally?) nullable and the null-state can be checked, your doesn't seem to be. \$\endgroup\$
    – alfC
    Commented Feb 13, 2019 at 21:46

1 Answer 1

4
\$\begingroup\$

There really isn't much to say here. This looks really good.

Typos

You have a couple typos in your code on your equality operators. You're comparing against other.p instead of other._p.

Not restrictive enough

We have this constructor:

constexpr explicit contiguous_iterator(T* p);

But that means if we have contiguous_iterator<Base>, we can construct it with a Derived*. We definitely need to disallow this.

On a similar vein, rebind<U> should be restricted to those Us which are the same type as T and are at least as cv-qualified.

Add another alias

Rather than writing this:

constexpr explicit contiguous_iterator(contiguous_iterator_base<std::remove_cv_t<T>> other) noexcept : decltype(other)(other) {}

If you added a using base = contiguous_iterator_base<std::remove_cv_t<T>>;, you could write:

constexpr explicit(base other) noexcept : base(other) {}

which makes way more sense.

Missing alias

You're using the alias templates for the type traits throughout, except you're using typename std::conditional<X>::type instead of std::conditional_t.

Line widths

Some of these lines are really long! Add some line breaks.

Why?

I guess lastly... what does contiguous_iterator<T> accomplish that simple T* doesn't?

\$\endgroup\$
2
  • \$\begingroup\$ Good ideas, though there was a reason for rebind. Adding force_rebind to cover the gap left by prudency.... After agonizing over the typedef you propose, I decided to add it as I don't need the name for anything else, like the base()-function in std::move_iterator. Regarding your why-question: Such iterators have the advantage of not being convertible to pointers, especially to base-subobjects. As it's a contiguous iterator, that's already everything... stackoverflow.com/questions/32701796/… \$\endgroup\$ Commented Dec 14, 2015 at 3:24
  • \$\begingroup\$ @Deduplicator Having a member like base() is good, except that in my opinion 1) it is not base class, 2) base can mean many things. underlying() seems to be a better name. In any case, I think contiguous iterators should (also) all have a data() function returning the pointer. \$\endgroup\$
    – alfC
    Commented Feb 13, 2019 at 21:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.