3

Lets say I have an interface. Interface has begin and end functions because derived classes have to implement for-range functionality. The user will only use interface and will not know about the implementation of derived classes. I cannot use the same iterator for all derived classes (more specifically, operator++() is different) so I have to make an abstract base iterator class.

class BaseIterator
{
    //...
public:
    virtual Type operator*()
    {
        //Implementation
    }

    virtual bool operator!=(const BaseIterator&)
    {
        //Implementation
    }

    virtual BaseIterator& operator++() = 0; 
}

//Interface
struct Interface
{
    //other pure virtual functions

    virtual BaseIterator& begin() = 0;
    virtual BaseIterator& end() = 0;
}

In concrete classes who inherit the interface I am using, lets say one of them is class A, each has its own iterator which inherits from BaseIterator, and uses it to implement begin and end functions.

class A : public Interface
{
//...

class AIterator : public BaseIterator
    {
        AIterator& operator++()
        {
            //...
        }
    }

    public:

    AIterator& begin() 
    {
        //...
    }

    AIterator& end() 
    {
        //...
    }


}

Similarly for other derived classes. The problem occurs when I try to use the for range loop with polymorphic types. For example (*)

Interface* c = Interface::makeA(); //assume for simplicity that there is static function in "Interface"

for(auto el : *c)
{
    //do something with el
}

I get an error that I cannot instantiate the abstract class because of the pure virtual function operator++(). The reason I think this happens is in the implementation of for-range loop which goes something equivalently to the following:

auto && __range = range_expression ;
for (auto __begin = __range.begin(), __end = __range.end(); __begin != __end; ++__begin) {
   range_declaration = *__begin;
   loop_statement;
}

I believe the problem lies in the "auto__begin == __range.begin()". begin returns reference to the BaseIterator, which because of the auto type deduction gets removed which in the end makes __begin type of BaseIterator and that is abstract class and cannot be instantiated. I know that this kind of behaviour can be implemented in Java. Am I missing something here? If not, how would you implement this but keeping the functionality in (*)?

  • 1
    Your `iterator` class can't use inheritance, it will end up getting [sliced](https://stackoverflow.com/questions/274626/what-is-object-slicing) by just about anyone trying to iterate through your objects, including a range-based `for` loop. Iterators need to follow value semantics and be copyable, which won't work well with your attempt to use inheritance. – Remy Lebeau Oct 11 '19 at 20:57
  • @RemyLebeau Yes I am familiar with slicing and aware that it is happening in my example. Do you have any idea for alternative design? –  Oct 11 '19 at 21:02
  • If your class A does not store an object of the iterator somewhere you will also have a memory leak in addition to slicing as you return a reference to a temporary (or a heap-allocated object which is not better). – Jens Oct 11 '19 at 21:04
  • @Jens Thanks for the comment but it constructs begin and end iterators in a constructor and stores it as private variables. –  Oct 11 '19 at 21:06

1 Answers1

0

Please post a full example showing the problem in the future. I guess that the error you are facing is something like (https://wandbox.org/permlink/PKop4WMbpuygHoes)

prog.cc:73:19: error: cannot allocate an object of abstract type 'BaseIterator'
   73 |     for(auto&& i: a) {
      |                   ^
prog.cc:3:7: note:   because the following virtual functions are pure within 'BaseIterator':
    3 | class BaseIterator
      |       ^~~~~~~~~~~~
prog.cc:19:27: note:     'virtual BaseIterator& BaseIterator::operator++()'
   19 |     virtual BaseIterator& operator++() = 0;
      |                           ^~~~~~~~
prog.cc:73:19: error: cannot declare variable '__for_begin ' to be of abstract type 'BaseIterator'
   73 |     for(auto&& i: a) {
      |                   ^
prog.cc:73:19: error: cannot allocate an object of abstract type 'BaseIterator'
prog.cc:73:19: error: cannot declare variable '__for_end ' to be of abstract type 'BaseIterator'

The problem is obvious because the for loop tries to assign the return value of begin() to a local variables which it can't.

C++ isn't Java. The ranged-based for loop either calls begin/end member functions or calls free functions, so there is no need for an interface to get the iterator. Your class only has to implement these functions and the range-based for will work. Besides that, having operator!= in the base class makes it possible to compare two iterators of different sub-types. To check this you need to use dynamic_castto check the types. You also need to down-cast the argument in the implementation which is obviously not a good design.

Inheritance is not so prominent for these kinds of problems in C++ and generic code using templates is preferred, e.g. in the STL. All the algorithms will work with a compatible iterator without inheritance because the iterator type is a template parameter.

Here is an example using a simple Range class as container to iterator over: #include

struct Range;

struct Iterator {
    Range const* r;
    int current;

    Iterator(Range const* x, int c) : r(x), current(c) {}

    Iterator& operator++() {
        ++current;
        return *this;
    }

    int const& operator*() const {
        return current;
    }
};

bool operator==(Iterator const& x, Iterator const& y) {
    return x.current == y.current;
}

bool operator!=(Iterator const& x, Iterator const& y) {
    return !(x == y);
}

struct Range {
    int min;
    int max;

    Iterator begin() const {
        return Iterator(this, min);
    }

    Iterator end() const {
        return Iterator(this, max+1);
    }
};


int main() {
    Range r{-5, 5};
    for(auto&& i: r) {
        std::cout << i << std::endl;
    }
}

This is a simplified and incomplete example only to show how to make it work with the range-based for loop. If you want to implement iterators that are conforming to the iterators in the standard library you need to add a bunch of member functions and typedefs. There should be plenty of information of how to write these iterators.

PS: If you decide to use the Java-like approach with interfaces don't forget to give your interfaces a (public or protected) virtual destructor and declare copy constructors as delete to prevent object slicing.

Jens
  • 9,058
  • 2
  • 26
  • 43
  • I know how to make iterators work with for-range loop when there are no inheritance and interfaces but the instructions for my problem were written with Java in mind so I tried to do it in Java-like approach thinking it will be easier to follow the instructions but apparently it's not. –  Oct 11 '19 at 21:37
  • "*the instructions for my problem were written with Java in mind so I tried to do it in Java-like approach*" - That won't work in C++ – Remy Lebeau Oct 11 '19 at 21:58
  • @RemyLebeau I guess I'll have to come up with a C++-like approach –  Oct 11 '19 at 22:28
  • "The problem is obvious because the for loop tries to assign the return value of begin() to a local variables which it can't." Isn't the problem that the type of that local variable is deduced to be abstract class? Isn't your program assigning return value of begin to a local variables in your simple Range example? –  Oct 11 '19 at 22:36
  • @zezanjee Yes and yes. The problem is that the for loop is defined in a way that an object of an abstract class should be created. However, even if the base class was not abstract, the program would have a slicing problem. – Jens Oct 13 '19 at 07:43
  • @zezanjee I am not sure what the question " Isn't your program assigning return value of begin to a local variables in your simple Range example?" is pointing to. My answer contains two code snippets. One in the online compiler example where I tried to reproduce your issue with code based on your incomplete snippet. Please post a full example next time. In the Range example, `begin` returns an object by value, and this is assigned to a local variable. Since Iterator is copyable, I don't see any issues here. – Jens Oct 13 '19 at 07:45
  • @Jens I was confused with the wording you used but it's clear now. I am aware of the slicing problems with this design and why it happens which is why I asked here for maybe alternative solutions. In the end, I didn't used any inheritance (which only complicates stuff in this case!) and imo in looks much simpler and nicer than the proposed Java-like model. Anyway thanks for you time, I am marking this as solved. –  Oct 13 '19 at 11:01