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 Ts 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::iteratorseems to become deprecated there. \$\endgroup\$friendfunctions 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. \$\endgroup\$sizeof(v) / sizeof(void*)can be replaced bystd::size(v)\$\endgroup\$