When implementing an iterator interface for a generic data type, I encountered the following recommendation: "... do not put virtual functions in a class template, unless you want all virtual functions to be instantiated (as opposed to non-virtual functions of template types)" [ C ++ Programming Standards, Recommendation No. 64 (Sutter, Allesandrescu) http://programming-lang.com/ru/comp_programming/satter/0/j133.html ]

For these reasons, I redid my old code:

template<class Item> class IIterator { public: virtual ~IIterator(); virtual void First() = 0; virtual void Next() = 0; virtual bool IsDone() const = 0; virtual Item * CurrentItem() const = 0; protected: IIterator(); } //... template<class T> class IteratorList: public IIterator<T> { ... } 

in the following way:

 class IIterator { public: virtual ~IIterator(); virtual void First() = 0; virtual void Next() = 0; virtual bool IsDone() const = 0; template<class Item> Item * CurrentItem() const { return nullptr; } protected: IIterator(); }; //... template<class T> class IteratorList: public IIterator { ... } 

As I understand it, my second edition of the iterator interface will allow:

    • separately compile modules that inherit this interface
    • avoid overriding my generic type

Did I understand and apply the above recommendation correctly?


Interesting, but if you implement CurrentItem as follows:

 template<class Item> Item* IIterator::CurrentItem() { assert(!"Incorrectly overridden"); return nullptr; } 

it seems to me and the template:

 template<class Item> Item * CurrentItem() const; 

and virtual:

 virtual Item * CurrentItem() const = 0; 

options CurrentItem wakes up equally fulfill your contract? Well, of course, except that the warning in the template version can only occur at the execution stage.

  • In the second variant, the CurrentItem function is not virtual; therefore, it cannot be redefined as a successor. Those. it always returns NULL. I find it hard to imagine an algorithm in which I need to operate with iterators, but it is unnecessary to have access to the values ​​they point to. If the algorithm should get access to the value, then in any case it should know its type, and the second option will not have advantages over the first one. - Chorkov
  • Yes, but this is not a reasonable combination - an absolutely unnecessary function for you CurrentItem ... - AR Hovsepyan
  • I wanted to get clarification in the comment, but I didn’t help with formatting, I decided to add a question. - user2421061

1 answer 1

Now your problem is that you cannot override CurrentItem . In the GOF book, the iterator was implemented based on the fact that the type of objects was known. In your case, it would be something like this:

 class IItem{ public: //интерфейс virtual ~IItem(){} }; class IIterator{ public: virtual IItem* CurrentItem() const = 0; //... }; 

But this solution is not applicable if the stored objects do not have a common interface. In this case, you somehow have to create your own iterator for each type. I see the following paths:


Do not think about it. In any case, until it became a bottleneck. There is an opinion that the code should be written so that it is primarily readable and followed. Any optimizations complicate and confuse the code. Your first version is quite clear and simple.


Trust type control to the user. Namely, to push everything through void* .

 virtual void* CurrentItem() const = 0; 

Very doubtful decision. A similar story was with collections in Java, before the appearance of generics (neutered patterns). Very good ground for mistakes.


Use only static polymorphism. This is done in the STL, it seems to work out pretty well. Only in this case, all users of your iterator will also become template. And in order to explicitly indicate the required interface, you will have to pervert with CRTP or wait for concepts (to be honest, I don’t know when they will be brought up).


Total. The recommendation is called "Reasonably combine static and dynamic polymorphism . " In my opinion the most sensible solution would be to leave as is. The remaining tricks only complicate and confuse the code.

  • > In my opinion the most reasonable solution would be to leave it as it is. Yes, I agree with you, the canonical monitor lizard iterator is not the best example for applying the above recommendation of Sutter and Alexandrescu. It would be interesting to see where this recommendation really helps to improve the code. The examples to which the book itself sends out seemed very complicated to me, I understood little there :) - user2421061