Returning different iterators with virtual derived methods

Viewed 1934

Say I have a virtual base class Base, which will in part behave like a container, with two derived classes VectorLike and RangeLike. I want to achieve something like the following:

class VectorLike : public Base {
    std::vector<int> data;
public:
    virtual std::vector<int>::const_iterator cbegin() { return data.cbegin() }
    virtual std::vector<int>::const_iterator cend() { return data.cend() }
}

class RangeLike : public Base {
    int min, max;
    class const_iterator {
        int x;
    public:
        int operator++() { return ++x }
        bool operator==( const_iterator rhs ) { return x == rhs.x }
        const_iterator( int y ) { x = y }
    }
public:
    virtual const_iterator cbegin() { return const_iterator( min ); }
    virtual const_iterator cend() { return const_iterator( max ); }
}

This code will not compile, since std::vector<int>::const_iterator and RangeLike::const_iterator aren't identical or covariant. To achieve the second, I would need an iterator base class from which both std::vector<int>::const_iterator and RangeLike::const_iterator will derive. But then still cbegin() and cend() will have to return pointers to iterators, which will make an even bigger mess.

My question is, is it possible to achieve something like the above code and if so how?

2 Answers

Here's a C++20 based solution (partial implementation)... just thought I'd put it out there.

It uses a variant with stack storage rather than polymorphism and dynamic allocation, so it might have better performance.

It should work for any given set of iterator types that have a common value_type and whose reference type is value_type&. It should also be fairly easy to adapt for other types of iterator and to include operator->.

Not claiming this is better than the implementation in the original answer. Merely an interesting alternative...

PS. normal in the names below simply indicates a basic pointer based iterator.

  template <typename _Iterator, typename _Value, typename _Reference, typename _Difference>
  concept forward_iterator_for
    = std::forward_iterator<_Iterator>
      && std::same_as<std::iter_value_t<_Iterator>, _Value>
      && std::same_as<std::iter_reference_t<_Iterator>, _Reference>
      && std::same_as<std::iter_difference_t<_Iterator>, _Difference>;

  template <typename _Iterator, typename _Value>
  concept normal_forward_iterator_for //
    = std::same_as<std::remove_cvref_t<_Value>, std::remove_const_t<_Value>>
      && forward_iterator_for<_Iterator, std::remove_const_t<_Value>, _Value&, std::ptrdiff_t>;

  template <typename _Value, normal_forward_iterator_for<_Value> ... _Iterator>
  class normal_forward_iterator_variant {
   public:
    using iterator_category = std::forward_iterator_tag;
    using difference_type   = std::ptrdiff_t;
    using value_type        = std::remove_cv_t<_Value>;
    using reference         = _Value&;
    using iterator_type     = std::variant<_Iterator...>;

   private:
    iterator_type _m_iter;

   public:
    normal_forward_iterator_variant() = default;

    template <normal_forward_iterator_for<_Value> _Iter, typename... _Args>
    normal_forward_iterator_variant(std::in_place_type_t<_Iter>, _Args&&... args)
    noexcept (std::is_nothrow_constructible_v<_Iter, _Args&&...>)
    : _m_iter(std::in_place_type<_Iter>, std::forward<_Args>(args)...) {}

    normal_forward_iterator_variant(iterator_type const& iter)
    noexcept (std::is_nothrow_copy_constructible_v<iterator_type>)
    : _m_iter(iter) {}

    normal_forward_iterator_variant(normal_forward_iterator_variant const&) = default;

    normal_forward_iterator_variant(normal_forward_iterator_variant&&) = default;

    constexpr normal_forward_iterator_variant&
    operator=(normal_forward_iterator_variant const& iter)
    noexcept (std::is_nothrow_copy_assignable_v<iterator_type>) //
    requires std::is_copy_assignable_v<iterator_type> {
      _m_iter = iter._m_iter;
      return *this;
    }

    constexpr normal_forward_iterator_variant&
    operator=(normal_forward_iterator_variant&& iter)
    noexcept (std::is_nothrow_move_assignable_v<iterator_type>) //
    requires std::is_move_assignable_v<iterator_type> {
      _m_iter = std::move(iter._m_iter);
      return *this;
    }

    template <typename _Tp>
    constexpr normal_forward_iterator_variant&
    operator=(_Tp const& x)
    noexcept (std::is_nothrow_assignable_v<iterator_type, _Tp&>) //
    requires std::is_assignable_v<iterator_type, _Tp&> {
      _m_iter = x;
      return *this;
    }

    template <typename _Tp>
    constexpr normal_forward_iterator_variant&
    operator=(_Tp&& x)
    noexcept (std::is_nothrow_assignable_v<iterator_type, _Tp&&>) //
    requires std::is_assignable_v<iterator_type, _Tp&&> {
      _m_iter = std::move(x);
      return *this;
    }

    [[nodiscard]] constexpr reference
    operator*() const noexcept {
      return std::visit([](auto&& iter) -> reference {
        return *iter;
      }, _m_iter);
    }

    constexpr normal_forward_iterator_variant&
    operator++() noexcept {
      std::visit([](auto&& iter) {
        ++iter;
      }, _m_iter);
      return *this;
    }

    constexpr normal_forward_iterator_variant
    operator++(int) noexcept {
      normal_forward_iterator_variant rv(*this);
      ++*this;
      return rv;
    }

    [[nodiscard]] friend constexpr bool
    operator==(normal_forward_iterator_variant const& a, normal_forward_iterator_variant const& b) noexcept {
      return (a._m_iter == b._m_iter);
    }
  };

Example use:

#include <iostream>
#include <iomanip>
#include <vector>
#include <list>

#include <iterator.h>

int
main() {
  using vector   = std::vector<int>;
  using list     = std::list<int>;
  using iterator = normal_forward_iterator_variant<const int, vector::const_iterator, list::const_iterator>;

  iterator iter;

  vector v{ 0, 1, 2 };
  iter = v.cbegin();
  std::cout << *iter++ << std::endl;
  std::cout << *iter << std::endl;
  std::cout << *++iter << std::endl;

  list l{ 3, 4, 5 };
  iter = l.cbegin();
  std::cout << *iter++ << std::endl;
  std::cout << *iter << std::endl;
  std::cout << *++iter << std::endl;
}

Output is 1, 2, ..., 6 as expected.

Related