Stefan Schroder
Stefan Schroder

Reputation: 3

How to extend the functionality of vector class as well as its iterator C++

I would like to find a clean way to slightly change the way std::vector operates.

Problem background

I need to be able to have index in the vector where the pointer would essentially slip one back, so for example; if my vector contains {0,1,2,3,4,5} and index [3] needs to slip, when iterating through the vector it should return: 0, 1, 2, 3, 3, 4, 5

Problem

Without rewriting the entire vector class and implementing my changes, is it possible to inherit std::vector<T> into a custom class, and override the behavior of the iterators ++ operator as well as the vector::begin()? (to add memory of the fact that it has already slipped one and not to slip again)

What I have tried

I have tried a basically implementation so far, but have gotten stuck when trying to override the begin() function.

#include <vector>
#include <iostream>
#include <iterator>

template <typename T>
class myVector : public std::vector<T>{
public:
  class myIterator : public std::vector<T>::iterator
  {
    // call vector's iterotor constructor

    //override
    // iterator& operator++(void){}
    // iterator operator++(int){}
  };

  using std::vector<T>::vector; // use the constructor from vector

  // extend the begin function, but include original operation
  myVector::myIterator begin() const
  {
    std::cout << "hi\n"; // testing to see if begin has been overriden properly
    return std::vector<T>::begin();
  }

};

// print out the vector
template <typename T> 
std::ostream& operator<<(std::ostream& os, const myVector<T>& v) 
{ 
  auto begin=v.begin();
  while (begin!=v.end())
  {
    os << *begin;
    ++begin;

    if(begin!=v.end())
    {
      os << ',';
    }
  }
  return os;
} 

int main() {
  myVector<int> vec = {1,2,3,4};

  std::cout << vec << '\n';
}

If you have an alternative clean solutions, it is also welcome.

Thanks

Upvotes: 0

Views: 629

Answers (2)

One Lyner
One Lyner

Reputation: 2004

As others already said, just define a custom iterator. If you moreover want the nice range iteration, you can then just define a custom range class returning your custom iterators.

See a working example below (needs --std=c++17):

#include <vector>
#include <iostream>

template<typename T>
class SlipIterator {
private:
  using iterator = typename std::vector<T>::const_iterator;
  iterator i;
  iterator slip;
  bool has_slipped;

public:
  SlipIterator(iterator i, iterator slip): i(i), slip(slip), has_slipped(false) {}

  SlipIterator &operator++() {
    if ((!has_slipped) && (i == slip))
      has_slipped = true;
    else
      ++i;
    return *this;
  }

  bool operator!=(SlipIterator<T> &the_end) const { return i != the_end.i; }

  const T &operator*() const { return *i; }

};

template<typename T>
class SlipperyRange {
private:
  const std::vector<T> &v;
  size_t slip_index;

public:
  SlipperyRange(const std::vector<T> &v, size_t slip_index) : v(v), slip_index(slip_index) {}

  SlipIterator<T> begin() const { return SlipIterator<T>(v.cbegin(), v.cbegin() + slip_index); }

  SlipIterator<T> end() const { return SlipIterator<T>(v.cend(), v.cend()); }
};

int main() {
  std::vector<int> v{1,2,3,4,5};
  for(const int i: SlipperyRange{v, 2})
    std::cout << i << ' ';
  std::cout << '\n';
  return 0;
}

Upvotes: 1

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122994

I would never inherit from std::vector publicly. It is just too easy to be used wrong and you have no control over misuse. std::vector has no virtual destructor!

Iterators seems to be the appropriate customization point. Much more is needed to get a fully compliant iterator, but this is enough for a working example:

#include <iterator>
#include <vector>
#include <iostream>

template <typename It>
struct slippery_iterator {
    bool slipped = false;
    It to_be_repeated;
    It iterator;
    slippery_iterator(It iterator,It to_be_repeated) : iterator(iterator),to_be_repeated(to_be_repeated){}

    slippery_iterator& operator++(){
        if (!slipped && iterator == to_be_repeated){
            slipped = true;
            return *this;
        }        
        ++iterator;        
        return *this;
    }
    typename std::iterator_traits<It>::reference operator*(){
        return *iterator;
    }
    bool operator!=(It other) { return iterator != other;}
};

template <typename It>
slippery_iterator<It> make_slippery_iterator(It it,It slip){
    return {it,slip};
}


int main() {
    std::vector<int> x{1,2,3,4,5};
    auto begin = make_slippery_iterator(x.begin(),x.begin()+2);
    for (; begin != x.end(); ++begin){
        std::cout << *begin;
    }
}

Output:

123345

PS: Note that I am used to C++11 where you need a make_x helper. It isnt needed with more recent standards.

Upvotes: 4

Related Questions