8

I have an abstract class that is basically meant to serve as an "iterator interface". That is, it is an abstract iterator that a few concrete classes will later implement. To make the abstract class an iterator, I need to overload T operator++(int), where T is the class for which the operator is overloaded.

class AbstractClass {
    virtual AbstractClass operator++(int) = 0; // compile time error
    virtual AbstractClass& operator++() = 0;   // but this is fine
}

However, I cannot write AbstractClass operator++(int) as only pointers and references to an abstract class are permitted as return values.

Is there a way to require the subclasses to overload operator++(int) in plain C++11?

foki
  • 8,624
  • 6
  • 33
  • 32
  • Aren't you already required to override it in order to be able to make instances of subclasses? (since it's a pure virtual method) – Alexey S. Larionov Jul 29 '20 at 19:02
  • 5
    @anishsane, That signifies post-increment instead of pre-increment. – chris Jul 29 '20 at 19:03
  • 4
    I don't think this can be done. The derived type is runtime abstracted, and the return type is compile time. The only way to do this is to have `AbstractClass` only _pretend_ to be abstract. – Mooing Duck Jul 29 '20 at 19:03
  • Ah... Interesting. Thank you! – anishsane Jul 29 '20 at 19:04
  • @MooingDuck To pretend would mean to simply omit the implementation of the function instead of writing `virtual ... = 0;`? – foki Jul 29 '20 at 19:19
  • 1
    "I have an abstract class that is basically meant to serve as an "iterator interface". A Java refugee I presume? – n. m. could be an AI Jul 29 '20 at 23:20
  • @n.'pronouns'm. Yep, you probably can count me as one. Java was what I was taught in school as an OO exemplar. How do you C++-idiomatically make an abstraction of a lazy iterator (generator)? It serves as an abstraction of a class that yields (a Python refugee too) one object of class T at a time and can be iterated through, sliced, etc. – foki Jul 30 '20 at 01:11
  • @n.'pronouns'm. I have [the more specific question](https://stackoverflow.com/questions/63158351/how-to-make-a-hierarchy-of-different-object-generators-in-plain-c11) too. Here to learn. :) – foki Jul 30 '20 at 01:17
  • @foki: C++ prefers compile time dispatch, so we don't need those abstractions on many standard components, like iterators. Compile time dispatch is preferred because it results in faster code. You cannot make a dynamic abstraction of a lazy iterator directly, iterators are always compile time. – Mooing Duck Jul 30 '20 at 01:48
  • C++ is not an OO language. It is a language that admits the OO paradigm among others. Iterators do not easily fit into the OO paradigm, at least not into the C++ version of it. There are no OO iterators in the standard C++ library simply because there are no idiomatic C++ OO iterators. You can create an OO iterator but it would be expensive and clunky. – n. m. could be an AI Jul 30 '20 at 10:28
  • 1
    You may also want to read [Why can't I do polymorphism with normal variables?](https://stackoverflow.com/questions/26188221/why-cant-i-do-polymorphism-with-normal-variables) Tour Q should be closed as a dupe with a reference to that. – n. m. could be an AI Jul 30 '20 at 10:31

1 Answers1

3

Abstract classes with virtual members are usually accessed through base pointers, so you teh caller doesn't know the derived type, but the return value from operator++(int) must be known at compile time by the caller, so there's no way to do this directly. I would simply not provide this method, and have your class be iterator-like, but not fully iterator-conforming.

However, there is a complex workaround, and that's to make a non-abstract iterator that itself can handle a virtual iterator as a member. This gets super complicated.

//abstract interface for iterators
template<class value_type>
struct virtual_iterator_interface {
    virtual ~virtual_iterator_interface(){};
    virtual std::unique_ptr<virtual_iterator_interface> clone()const=0;
    virtual value_type& deref()=0;
    virtual void increment()=0;
};
//wrapper implementation for iterators
template<class value_type, class It>
struct virtual_iterator : virtual_iterator_interface<value_type> {
    It it;
    virtual_iterator(It it_) : it(it_) {}
    std::unique_ptr<virtual_iterator_interface> clone()const
    {return std::make_unique<virtual_iterator>(it);}
    value_type& deref()
    {return *it;}
    void increment()
    {return ++it;}
};

static const struct from_iterator_t {} from_iterator;
// The iterator that holds a pointer to an abstracted iterator
template<class value_type>
class erased_iterator {
    std::unique_ptr<virtual_iterator_interface<value_type>> ptr;
public:
    template<class It>
    erased_iterator(from_iterator_t, It it)
    :ptr(std::make_unique<virtual_iterator<value_type,It>>(it)) {}
    erased_iterator(std::unique_ptr<virtual_iterator_interface<value_type>> ptr_)
    :ptr(std::move(ptr_)) {}
    erased_iterator(const erased_iterator& rhs)
    :ptr(rhs.ptr->clone()) {}
    erased_iterator(erased_iterator&& rhs) = default;
    erased_iterator& operator=(const erased_iterator& rhs)
    {ptr=rhs.ptr->clone();}
    erased_iterator& operator=(erased_iterator&& rhs) = default;

    //TADA! Iterator things are now possible!
    value_type& operator*(){return ptr->deref();}
    erased_iterator& operator++(){ptr->increment(); return *this;}
    erased_iterator operator++(){erased_iterator t(it->clone()); ptr->increment(); return t;}
};

In addition to complexity, this allocates iterators on the heap, which makes them very slow.

Also note: virtual members prevent inlining and optimization, so makes the code slower. For things like iterators, which are supposed to be lightweight, this can actually make the code a lot slower.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • I don't quite get "Abstract classes with virtual members runtime-erase the derived type, but the return value from operator++(int) must be known at compile time." What type is erased by whom? Also, if whatever type is erased in runtime then it's known in compile time, no? – foki Jul 29 '20 at 19:26
  • 1
    @foki: The code that constructs the instance knows the type at compile time, but the code that calls the method calls it through a base pointer, and doesn't know the type, even at compile time. I reworded it to be clearer. – Mooing Duck Jul 29 '20 at 20:30