Ghosty Frosty
Ghosty Frosty

Reputation: 168

Is there any way to modify a nested classes implementation in it's children?

I want to have a class interface (let's call it class A) that can be inherited and implemented differently throughout my code, and that interface contains some functions, including an stl type iterator, which is defined as a nested class.

My problem is that whenever i try to inherit the interface (into a let's call class B), as well as the iterator, and implement them, the begin and end of the inherited class expect an iterator of type A::iterator, even though the iterator in the B class inherits from A::iterator. I know that polymorphism works only with pointers/references, but how would i be able to do this one?

I also tried doing it with friend classes, but i don't really know that well how they work, as I've never used them before, so it turned out kind of messy. Here is some example code (the one that i actually run)

class A
{
public:
    class iterator 
    {
    public:
        virtual Entity& operator*() const = 0;
        virtual bool operator!=(const iterator) const = 0;
        virtual bool valid() const = 0;
        virtual iterator& operator=(const iterator& it) = 0;
        virtual iterator operator++() = 0;
        virtual iterator operator++(int) = 0;
        virtual ~iterator() {};
    };
    virtual int add(const std::vector<std::string>& params) = 0;
    virtual int remove(const std::vector<std::string>& params) = 0;
    virtual int update(const std::vector<std::string>& params) = 0;
    virtual int save(const std::vector<std::string>& params) = 0;
    virtual int size() = 0;
    virtual typename iterator begin() = 0;
    virtual typename iterator end() = 0;
    virtual ~A() {}
};


class B: public A
{
private:
    std::vector<Entity> elements;
public:
    class iterator : public A::iterator
    {
    private:
        std::vector<Entity>::iterator ptr;
        B& repo;
    public:
        iterator(std::vector<Entity>::iterator ptr, B& container) : ptr{ ptr }, repo{ container }{}
        Entity& operator*() const override;
        bool operator!=(const iterator) const override;
        bool valid() const override;
        iterator& operator=(const iterator& it) override;
        iterator operator++() override;
        iterator operator++(int) override;
    };
    B() : elements{ std::vector<Entity>() } {}
    int add(const std::vector<std::string>& params) override;
    int remove(const std::vector<std::string>& params) override;
    int update(const std::vector<std::string>& params) override;
    int save(const std::vector<std::string>& params) override;
    int size() override;
    typename iterator begin();
    typename iterator end();
    ~B() {};
};

The reason i want to do this is because i have to create 2 different repositories, one working with a file and one in memory, and maybe a future one requiring a database, but i just can't get the iterator right by inheriting it. Returning an std::vector would be a lot faster but also kind of cheating. EDIT: What I'm ultimately trying to achieve is to have two repositories that have the same interface and that can also be iterated through, one of them implemented on an std::vector and the other one directly on a file. That means that the iterator for the vector repo would have to just give me the const std::vector iterator and the other one would have to open a file, go to the next line, etc in a read only manner. I have to make them compatible with the range based for loop.

Upvotes: 0

Views: 125

Answers (2)

aschepler
aschepler

Reputation: 72431

It seems you're looking for a type-erasure: a type that supports different implementations, but can be used by other code as just a single type, as that type, not just by pointer and reference, can be copied, etc. The usual way of setting this up is a non-polymorphic class which contains a pointer to a polymorphic interface:

#include <utility>
#include <memory>
#include <vector>

struct Entity {};

class A
{
protected:
    class IterInterface
    {
    public:
        virtual ~IterInterface() = default;
        virtual std::unique_ptr<IterInterface> clone() const = 0;
        virtual Entity& dereference() const = 0;
        virtual void increment() = 0;
        virtual bool equal_to(const IterInterface& other) const = 0;
    };

    virtual std::unique_ptr<IterInterface> begin_impl() = 0;
    virtual std::unique_ptr<IterInterface> end_impl() = 0;

public:
    class iterator
    {
    public:
        using iterator_category = std::forward_iterator_tag;
        using value_type = Entity;
        using reference = Entity&;
        using pointer = Entity*;
        using difference_type = std::ptrdiff_t;

        iterator() = default;
        iterator(const iterator& it)
        {
            if (it.m_impl)
                m_impl = it.m_impl->clone();
        }
        iterator(iterator&&) noexcept = default;
        iterator& operator=(const iterator& it)
        {
            if (it.m_impl)
                m_impl = it.m_impl->clone(); // self-assignment-safe
            else
                m_impl = nullptr;
            return *this;
        }
        iterator& operator=(iterator&&) noexcept = default;

        explicit iterator(std::unique_ptr<IterInterface> && impl)
            : m_impl(std::move(impl)) {}

        Entity& operator*() const
        { return m_impl->dereference(); }

        iterator& operator++()
        { m_impl->increment(); return *this; }
        iterator operator++(int) const
        {
            iterator copy = *this;
            m_impl->increment();
            return copy;
        }

        friend bool operator==(const iterator &it1, const iterator &it2)
        {
            bool it1_ok(it1.m_impl);
            bool it2_ok(it2.m_impl);
            if (it1_ok && it2_ok)
                return it1.m_impl->equal_to(*it2.m_impl);
            else
                return it1_ok == it2_ok;
        }
        friend bool operator!=(const iterator &it1, const iterator &it2)
        { return !(it1 == it2); }

    private:
        std::unique_ptr<IterInterface> m_impl;
    };

    iterator begin()
    { return iterator(begin_impl()); }
    iterator end()
    { return iterator(end_impl()); }

    // ...
};

class B : public A
{
public:
    // ...
private:
    std::vector<Entity> elements;

    class IterImpl : public IterInterface
    {
    public:
        explicit IterImpl(std::vector<Entity>::iterator viter)
            : m_viter(viter) {}

        std::unique_ptr<IterInterface> clone() const override
        { return std::make_unique<IterImpl>(m_viter); }

        Entity& dereference() const override { return *m_viter; }

        void increment() override { ++m_viter; }

        bool equal_to(const IterInterface& other) const override
        {
            if (auto* oimpl = dynamic_cast<const IterImpl*>(&other))
                return m_viter == oimpl->m_viter;
            // "other" isn't even from a B.
            return false;
        }

    private:
        std::vector<Entity>::iterator m_viter;
    };

    std::unique_ptr<IterInterface> begin_impl() override
    { return std::make_unique<IterImpl>(elements.begin()); }

    std::unique_ptr<IterInterface> end_impl() override
    { return std::make_unique<IterImpl>(elements.end()); }
};

Upvotes: 1

Ghosty Frosty
Ghosty Frosty

Reputation: 168

I managed to solve the issue quite bluntly to say so, by returning references to iterator rather than objects. With that i always have to keep a pointer that i can reference and return in the main function, but at least it works. The problem is as someone mentioned in the comments: polymorphism only works with pointers and references.

Upvotes: 0

Related Questions