简体   繁体   中英

Inheriting a abstract iterator interface

I want to inherit a iterator for an container class. It should always have the same signature independent of the actual implementation.

But I have the problem that in ContainerImpl the returned Container::Iterator from ContainerImpl::begin() slices the overridden methods from ContainerImpl::Iterator back to the ones of Container::Iterator .

How can I force the more specific method to be used? Or can I rewrite my classes so that it behaves like that?

Below is a working example. It compiles at least against C++17.

#include <tuple>

using std::tuple;

class Container {
    // some code

public:

    class Iterator {
    public:
        virtual tuple<int, int> &operator*() {};

        virtual Iterator &operator++() {};

        virtual bool operator!=(const Iterator &rhs) const {};
    };

    virtual Iterator begin() = 0;

    virtual Iterator end() = 0;
};

class ContainerImpl : public Container {
    // some code

public:

    class Iterator : public Container::Iterator {
    public:
        Iterator(ContainerImpl &containerImpl) {
            // init
        }

        Iterator() {
            // end_iterator
        }

        virtual tuple<int, int> &operator*() override {
            // impl
        };

        virtual Container::Iterator &operator++() override {
            // impl
        };

        virtual bool operator!=(const Container::Iterator &rhs) const override {
            // impl
        };
    };

    virtual Container::Iterator begin() override {
        return Iterator{*this};
    };

    virtual Container::Iterator end() override {
        return Iterator{};
    };
};


int main() {
    ContainerImpl cont{};
    for (auto &entry : cont) {
        // here Container::Iterator::operator++() is called
        // instead of ContainerImpl::Iterator::operator++()
    }
}

edit:

Thanks! I read through your comments and figured out that I should try an static approach using templates. Performance is quite important for those iterators. This will need refactoring of other code where I use this code. But it should be moderate.

Here is what I came up with:

#include <tuple>
#include <type_traits>

using std::tuple;

template<class SubContainer>
class Container;


template<class ContImpl>
class Iterator {
    static_assert(std::is_base_of<Container<ContImpl>, ContImpl>::value, "ContImpl not derived from Container");
public:

    tuple<int, int> &operator*();

    Iterator<ContImpl> &operator++();

    bool operator!=(const Iterator<ContImpl> &rhs) const;
};



template<class SubContainer>
class Container {
public:
    Iterator<SubContainer> begin();

    Iterator<SubContainer> end();

};

class ContImpl;

template<>
class Iterator<ContImpl> {
public:
    ContImpl &container;

    Iterator(ContImpl &container, bool is_end = false) : container(container) { init(); }

    void init() {
    }

    tuple<int, int> &operator*() {
    }

    Iterator<ContImpl> &operator++() {
    }

    bool operator!=(const Iterator<ContImpl> &rhs) const {
    }
};

class ContImpl : public Container<ContImpl> {
public:
    Iterator<ContImpl> begin() {
        return Iterator<ContImpl>(*this, false);
    }

    Iterator<ContImpl> end() {
        return Iterator<ContImpl>(*this, true);
    }
};


int main() {
    ContImpl cont{};
    for (auto &entry : cont) {
        // Iterator<ContImpl>::operator++() is called.
    }
}

If you want to dynamically abstract away the differences between, say, std::list<int> and std::vector<int> , then what you want is type erasure . But do note that, using things like template template parameters, it is also possible to statically abstract over containers with similar interfaces (with the same or different element types), which (if it meets your requirements) should be significantly faster.

Regarding your template-based approach: while it might have value as documentation, the methods in Container are unnecessary: static polymorphism does not need any “base functions” to override. If you remove them, you can do without that CRTP class entirely. Then, similarly, you can replace the Iterator class template with a simple class ContImpl::iterator .

Of course, at that point all that's left is the same interface compatibility as between the standard containers (which allows code like

template<template<class> class C>
C<int> makeInts(/*...*/);

void f() {
  auto d=makeInts<std::deque>(/*...*/);
  // ...
}

that I suggest above). The interesting exercise is to hide a different container interface behind wrapper classes or with a traits interface.

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM