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 input argument or underlying container type respectively. A typical example is qsort
which is located in <cstdlib>
.
Nowadays, when dealing with legacy code-bases or code written by dinosaurs, it's highly probable that you might stumble on such kind of data structures that most probably keep their elements in void**
buffers. The primary goal of-course would be to gradually move the code base towards use of modern STL containers and algorithms until these old data-structures become obsolete.
However, there are also the dinosaurs. Often, very large dinosaurs like a tyrannosaur that happens to be your manager. In order to convince them of the C++/STL superiority with out questioning the "usability" of the legacy data-structures that are being functioning all these years without a problem and most probable the author is one of them, 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.).
Down below lies in a very early stage such an iterator:
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. Supposedly, I had the following class
(e.g., Foo
):
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};
And then use for example std::sort
to sort v
with respect the Foo
objects that indirectionally contains:
std::sort(Iterator<Foo>(v), Iterator<Foo>(v + sizeof(v) / sizeof(void*)));
I've been careful to inherit my iterator from the 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 contiguous buffer
of T
s with what ever implications that might have.
Is this iterator scheme safe? Or are there any quirks or oddities that I must be aware of?
std::iterator
seems to become deprecated there. – davidhigh Oct 19 '16 at 9:34friend
functions here (because of auto conversions that can be done by the compiler). But I can't think of a situation where it would fail (so only a comment rather than a review). The rest seems fine. – Loki Astari Oct 19 '16 at 12:01sizeof(v) / sizeof(void*)
can be replaced bystd::size(v)
– Loki Astari Oct 19 '16 at 12:12