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*)));
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 T
s 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?