Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement standard iterators in class

I have classes which are usually using standard containers as underlying fields. For example, I have a class

template <typename T>
class Vec_3D
{
public:
    /* ... */
    std::array<T, 3> vec;
    /* ... */
};

which has only one variable vec and the rest are just functions I need when working with vectors. I want to be able to use range-based for loop such as

Vec_3D<double> vec;
for (double val : vec) {/*...*/}

which should obviusly iterate over std::array<double, 3>.

How to implement iterators in my class which should in turn call iterators of std::array<T, 3>?

I started with this question and tried to define iterators in my class as

typedef std::iterator<std::random_access_iterator_tag, T, ptrdiff_t, T*, T&> iterator;
typedef std::iterator<std::random_access_iterator_tag, const T, ptrdiff_t, const T*, const T&> const_iterator;

inline iterator begin() noexcept { return vec.begin(); }
inline const_iterator cbegin() const noexcept { return vec.cbegin(); }
inline iterator end() noexcept { return vec.end(); }
inline const_iterator cend() const noexcept { return vec.end(); }

but got compiling errors

error: no match for ‘operator!=’ (operand types are ‘Vec_3D<double>::iterator {aka std::iterator<std::random_access_iterator_tag, double, long int, double*, double&>}’ and ‘Vec_3D<double>::iterator {aka std::iterator<std::random_access_iterator_tag, double, long int, double*, double&>}’)

and operator++, operator*

like image 935
Michal Avatar asked Sep 26 '17 16:09

Michal


3 Answers

std::iterator is (was) a helper type to define the typedefs that a typical iterator requires. These typedefs within the class in turn make std::iterator_traits work with your iterator.

It does not, however, actually implement the required operations for you.

It was deprecated, because the std committee didn't like specifying that standard iterators had to have those typedefs, and writing the typedefs was not much bulkier than figuring out what arguments to pass to the std::iterator template.

The easy thing to do here is to just steal your underlying container's iterator. This makes your abstraction leak, but it is efficient and easy.

template <typename T>
struct Vec_3D {
  using container=std::array<T, 3>;
  using iterator=typename container::iterator;
  using const_iterator=typename container::const_iterator;

  iterator begin() { return vec.begin(); }
  iterator end() { return vec.end(); }
  const_iterator begin() const { return vec.begin(); }
  const_iterator end() const { return vec.end(); }
private:
  /* ... */
  container vec;
  /* ... */
};

If you don't want to expose your underlying container type, if you are willing to guarantee your underlying container is a contiguous buffer you can do:

template <typename T>
struct Vec_3D {
  using iterator=T*;
  using const_iterator=T const*;

  iterator begin() { return vec.data(); }
  iterator end() { return vec.data()+vec.size(); }
  const_iterator begin() const { return vec.data(); }
  const_iterator end() const { return vec.data()+vec.size(); }
private:
  /* ... */
  std::array<T,3> vec;
  /* ... */
};

as pointers are valid iterators.

If you find you are writing this "I am a modified container" boilerplate too much, you can automate it:

template<class Container>
struct container_wrapper {
  using container=Container;

  using iterator=typename container::iterator;
  using const_iterator=typename container::const_iterator;

  iterator begin() { return m_data.begin(); }
  iterator end() { return m_data.end(); }
  const_iterator begin() const { return m_data.begin(); }
  const_iterator end() const { return m_data.end(); }
protected:
  Container m_data;
};

and then

template <typename T>
class Vec_3D:private container_wrapper<std::array<T,3>> {
  // ...
};

but even that might be a bit much, why not just:

template <typename T>
class Vec_3D:public std::array<T,3> {
  // ...
};

It is true that deleting Vec_3D through a pointer to base is undefined behavior, but who deletes pointers to standard containers?

If this worries you:

template <typename T>
class Vec_3D: private std::array<T,3> {
  using container = std::array<T,3>;
  using container::begin();
  using container::end();
  // ...
};

lets you inherit privately, then bring certain operations back into scope.

like image 151
Yakk - Adam Nevraumont Avatar answered Oct 28 '22 11:10

Yakk - Adam Nevraumont


std::iterator is a base class only, its basically a container for some traits, but if you wanted to use it to implement your own iterator class you'd need to derive from it.

However you don't need to use it, there was a proposal to deprecate it, you could just define those traits directly in an iterator that you write. The following question has info on the proposal and help with implementing an iterator class:- Preparation for std::iterator Being Deprecated

At the moment you're defining your container's iterator types using that base, not a class that can actually do any iterating, which is why it fails.

You expose the array as a public member. If you're happy to expose that your vec_3d is implemented using an array (whether you continue to expose the member array publicly or not) then you could just use the array's iterators - its not clear from the question that your iterator needs any bespoke behaviour just because your container adds some functionality.

like image 35
ROX Avatar answered Oct 28 '22 10:10

ROX


A range-based for loop only requires that your class have begin() and end() methods (or overloads of std::begin() and std::end()) that return iterators. It doesn't care where those iterators come from. So, the simplest solution is to just use the array's own iterators instead of trying to define your own, eg:

template <typename T>
class Vec_3D
{
public:
    typedef typename std::array<T, 3> array_type;
    typedef typename array_type::iterator iterator;
    typedef typename array_type::const_iterator const_iterator;
    // or:
    // using array_type = std::array<T, 3>;
    // using iterator = array_type::iterator;
    // using const_iterator = array_type::const_iterator;
    ...

    inline iterator begin() noexcept { return vec.begin(); }
    inline const_iterator cbegin() const noexcept { return vec.cbegin(); }
    inline iterator end() noexcept { return vec.end(); }
    inline const_iterator cend() const noexcept { return vec.cend(); }
    ...

private:
    array_type vec;
};
like image 31
Remy Lebeau Avatar answered Oct 28 '22 12:10

Remy Lebeau