I want to make interface classes ForwardIterator
, BiderctionalIterator
and RandomAccessIterator
, each one of them has some operators (all of them are pure virtual and not implemented). Now if I want to implement an iterator for a container, I just inherit from the right iterator and get the help of the compiler if I accidently forgot to implement some functions/operators. Doing it with pure virtual functions works perfectly but It has the overhead of vtable which is unnecessary since the all code needs can be defined in compile time.
template <typename T>
struct ForwardIterator{
virtual T operator++() = 0;
virtual T operator++(int) = 0;
};
template <typename T>
struct BidirectionalIterator: public ForwardIterator<T>{
virtual T operator--() = 0;
virtual T operator--(int) = 0;
};
template <typename T>
struct RandomAccessIterator: public Bidirectional<T>{
virtual T operator+(int) = 0;
virtual T operator-(int) = 0;
};
// Custom Iterator Implementation
class MyCustomRandomAccessIterator
: public RandomAccessIterator<MyCustomRandomAccessIterator>{
// get errors if I miss some function definitions.
// but it has vtable !!!
};
If I understand correctly, you want to be sure that some methods are implemented in the final derived classes but without using the classic way (abstract inheritance) because it's too heavy.
My first idea was simply declare and not define the methods
template <typename T>
struct ForwardIterator {
T operator++ ();
T operator++ (int);
};
so when you use the methods
MyCustomRandomAccessIterator i;
++i;
you get a linker error.
But you get a linker error (and I suppose you prefer a compilation error) and only when you use a forgotten method, so if you forget a single method can be difficult detect it (you need to use it with an object).
So my idea, maybe not perfect, is delete
the methods, add a constructor and check the existence of the methods in the final class.
I mean... something as
template <typename T>
struct ForwardIterator {
T operator++ () = delete;
T operator++ (int) = delete;
ForwardIterator ()
{
static_assert( sizeof(decltype(std::declval<T>()++)), "!" );
static_assert( sizeof(decltype(++std::declval<T>())), "!" );
}
};
This way you get all compilation errors (maybe, if you want, with meaningful error messages, better than "!"
) simply declaring an object of your class
MyCustomRandomAccessIterator i;
// ++i; // no needs of invoke the forgotten methods to get the errors
The following is a full compiling example
#include <utility>
template <typename T>
struct ForwardIterator {
T operator++ () = delete;
T operator++ (int) = delete;
ForwardIterator ()
{
static_assert( sizeof(decltype(std::declval<T>()++)), "!" );
static_assert( sizeof(decltype(++std::declval<T>())), "!" );
}
};
template <typename T>
struct BidirectionalIterator: public ForwardIterator<T> {
T operator-- () = delete;
T operator-- (int) = delete;
BidirectionalIterator ()
{
static_assert( sizeof(decltype(std::declval<T>()--)), "!" );
static_assert( sizeof(decltype(--std::declval<T>())), "!" );
}
};
template <typename T>
struct RandomAccessIterator: public BidirectionalIterator<T>{
T operator+ (int) = delete;
T operator- (int) = delete;
RandomAccessIterator ()
{
static_assert( sizeof(decltype(std::declval<T>()+0)), "!" );
static_assert( sizeof(decltype(std::declval<T>()-0)), "!" );
}
};
// Custom Iterator Implementation
struct MyCustomRandomAccessIterator
: public RandomAccessIterator<MyCustomRandomAccessIterator> {
// with `if 0` (forgetting definition of requested methods) you get
// a lot of compilation errors
#if 1
MyCustomRandomAccessIterator operator++ ()
{ return *this; }
MyCustomRandomAccessIterator operator++ (int)
{ return *this; }
MyCustomRandomAccessIterator operator-- ()
{ return *this; }
MyCustomRandomAccessIterator operator-- (int)
{ return *this; }
MyCustomRandomAccessIterator operator+ (int)
{ return *this; }
MyCustomRandomAccessIterator operator- (int)
{ return *this; }
#endif
};
int main()
{
// simply declaring i you get a lot of errors, in case of no
// methods definitions
MyCustomRandomAccessIterator i;
}
What you want is the curiously recurring template pattern https://en.m.wikipedia.org/wiki/Curiously_recurring_template_pattern It allows for static polymorphism, ie you can cast the 'this' pointer to a pointer of child class for which you pass the type as a template parameter and call methods from the child class in the base class without a vtable. To differentiate the type of iterator you also pass a iterarator tag corresponding to the type as a template argument to the base class.This is how Boost.STLInterfaces works for example ( https://www.boost.org/doc/libs/1_74_0/doc/html/stl_interfaces.html ).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.