5

In the old days (when C was the predominant language under the hood and STL/templates were Alex Stepanov's dream), in order for programmers to achieve generality for functions and data-containers, the void* was used as the input argument (for function generality) or the underlying container type (for data-container generality). A typical example is qsort, which is located in <cstdlib>.

Now, when dealing with legacy code-bases or code written by dinosaurs, it's highly probable that you'll stumble upon data structures designed under this paradigm, ones that keep their elements in void** buffers. One of your primary goals should be to move the code-base gradually towards use of modern STL containers and algorithms until these old data structures become obsolete. However, there are also the dinosaurs to contend with, maybe a tyrannosaur that happens to be your manager. In order to convince them of the C++/STL superiority without questioning the "usability" of the legacy data structures that have functioned all these years without a problem and without offending them (since they might be the original author), I decided to engage the issue politically.

What I thought is to craft a template iterator that could deal with such void** buffers and would act as a bridge with the STL algorithms (e.g., std::sort, std::copy, etc.).

Below is one such iterator in a very early stage:

template<typename T>
class Iterator : public std::iterator<std::bidirectional_iterator_tag, T> {
  using T_ptr = std::remove_pointer_t<T>*;
  void  **pos;
public:  
  Iterator(void **pos_) : pos(pos_) {  }
  bool operator==(Iterator const &other) const { return pos == other.pos; }
  bool operator!=(Iterator const &other) const { return pos != other.pos; }
  bool operator<( Iterator const &other) const { return pos <  other.pos; }
  bool operator>( Iterator const &other) const { return pos >  other.pos; }
  bool operator<=(Iterator const &other) const { return pos <= other.pos; }
  bool operator>=(Iterator const &other) const { return pos >= other.pos; }
  Iterator& operator++() { 
    ++pos;
    return *this;
  }
  Iterator operator++(int) {
    Iterator out(*this);
    ++pos;
    return out;
  }
  Iterator& operator--() { 
    --pos;
    return *this;
  }
  Iterator operator--(int) {
    Iterator out(*this);
    --pos;
    return out;
  }
  Iterator& operator+=(int const n) {
    pos += n;
    return *this;
  }
  Iterator& operator-=(int const n) {
    pos -= n;
    return *this;
  }
  T& operator[](int const n) { *static_cast<T_ptr>(*(pos + n)); }
  T& operator*() { return *static_cast<T_ptr>(*pos); }
  T_ptr operator->() { return static_cast<T_ptr>(*pos); }
  friend Iterator operator+(Iterator const &lhs, int const n) {
    Iterator out(lhs);
    out.pos += n;
    return out;
  }
  friend Iterator operator-(Iterator const &lhs, int const n) {
    Iterator out(lhs);
    out.pos -= n;
    return out;  
  }
  friend Iterator operator+(int const n, Iterator const &rhs) {
    Iterator out(rhs);
    out.pos += n;
    return out;
  }
  friend Iterator& operator-(int const n, Iterator const &rhs) {
    Iterator out(rhs);
    out.pos -= n;
    return out;  
  }
  friend int operator-(Iterator const &A, Iterator const &B) { return B.pos - A.pos; }
};

My ambition is to use this iterator in the following manner. Suppose I had the following class:

struct Foo {
  int val = 0;

  explicit Foo(int val_) : val(val_) {}
           Foo()           = default;
           Foo(Foo const&) = default;
           Foo(Foo &&)     = default;

  Foo& operator=(Foo const&) = default;
  Foo& operator=(Foo &&)     = default;

  bool operator< (Foo const& rhs) const { return val <  rhs.val; }
  bool operator==(Foo const& rhs) const { return val == rhs.val; }
};

And the following buffer of void*:

Foo f1(1), f2(2), f3(3), f4(4);
void* v[] = {&f4, &f2, &f1, &f3};

I want to use std::sort to sort v directionally with respect to the Foo objects that it contains:

std::sort(Iterator<Foo>(v), Iterator<Foo>(v + sizeof(v) / sizeof(void*)));

Live Demo

I've been careful to inherit my iterator from std::iterator<std::bidirectional_iterator_tag, T> and not std::iterator<std::random_access_iterator_tag, T> in order to avoid treating the void** buffer as a contiguous buffer of Ts with what ever implications that might have.

Q

Is this iterator scheme safe? Are there any quirks or oddities that I must be aware of?

TriskalJM
  • 2,393
  • 1
  • 19
  • 20
101010
  • 41,839
  • 11
  • 94
  • 168
  • 2
    I'm voting to close this question as off-topic because it belongs on [CodeReview.se] – MSalters Oct 19 '16 at 07:06
  • 1
    Re-voting to close agreeing with MSalters's comment: "I'm voting to close this question as off-topic because it belongs on Code Review" – The Vee Feb 09 '18 at 13:38
  • 1
    This question was already cross-posted to CR: https://codereview.stackexchange.com/questions/144626/iterator-for-void-pointer-ranges – Zeta Feb 09 '18 at 13:49
  • I'm voting to close this question as off-topic because it is much too broad for Stack Overflow, which deals with more focused coding problems. For advice on improving working code, consider [codereview.se] instead - but do read [A guide to Code Review for Stack Overflow users](//codereview.meta.stackexchange.com/a/5778) first, as some things are done differently over there!. – Toby Speight Feb 09 '18 at 15:04

0 Answers0