C++: Polymorphic container / iterator vs compile time concept / traits

Background

This is purely for educational purposes. If you don't want to read the whole background, you can skip to the question at the bottom.

I have written a Queue interface (abstract class), and 2 derived implementations based on resizing arrays and linked lists.

template <typename T>
class IQueue {
public:
  virtual void enqueue(T item) = 0;
  virtual T dequeue() = 0;
  virtual bool isEmpty() = 0;
  virtual int size() = 0;
}

template <typename T>
class LinkedListQueue : public IQueue<T> {...}

template <typename T>
class ResizingArrayQueue : public IQueue<T> {...}

I wanted to be able to go over a queue's elements with an STL compliant iterator (I know queues should not be iterable), so I can use for (auto e: c) or queue.begin() / queue.end().

Because I use run-time polymorphism I had to add a client iterator class to IQueue and use the Pimpl idiom to instantiate the actual implementation specific iterators in the derived queue classes, to avoid object slicing issue. So the augmented code looks like:

template <typename T>
class IQueue {
public:
    virtual void enqueue(T item) = 0;
    virtual T dequeue() = 0;
    virtual bool isEmpty() = 0;
    virtual int size() = 0;

public:
    class IteratorImpl {
    public:
        virtual void increment () = 0;
        virtual bool operator== (const IteratorImpl& other) const = 0;
        virtual bool operator!= (const IteratorImpl& other) const = 0;
        virtual T& operator* () const = 0;
        virtual T& operator-> () const = 0;
        virtual void swap (IteratorImpl& other) = 0;
        virtual IteratorImpl* clone() = 0;
    };

public:
    class ClientIterator : public std::iterator<std::forward_iterator_tag, T> {
        std::unique_ptr<IteratorImpl> impl;

    public:
        ClientIterator(const ClientIterator& other) : impl(other.impl->clone()) {}
        ClientIterator(std::unique_ptr<IteratorImpl> it) : impl(std::move(it)) {}
        void swap(ClientIterator& other) noexcept {
            impl->swap(*(other.impl));
        }

        ClientIterator& operator++ () {
            impl->increment();
            return *this;
        }

        ClientIterator operator++ (int) {
            ClientIterator tmp(*this);
            impl->increment();
            return tmp;
        }

        bool operator== (const ClientIterator& other) const {
            return *impl == *other.impl;
        }

        bool operator!= (const ClientIterator& other) const {
            return *impl != *other.impl;
        }

        T& operator* () const {
            return **impl;
        }

        T& operator-> () const {
            return **impl;
        }
    };

    typedef ClientIterator iterator;

    virtual iterator begin() = 0;
    virtual iterator end() = 0;
};

and one of the derived classes implements the begin() / end() methods and the derived Iterator implementation:

template <typename T>
class LinkedListQueue : public IQueue<T> {
// ... queue implementation details.
public:
    class LinkedListForwardIterator : public IQueue<T>::IteratorImpl {
    // ... implementation that goes through linked list.
    };

    typename IQueue<T>::ClientIterator begin() {
        std::unique_ptr<LinkedListForwardIterator> impl(new LinkedListForwardIterator(head));
        return typename IQueue<T>::iterator(std::move(impl));
    }

    typename IQueue<T>::ClientIterator end() {
        std::unique_ptr<LinkedListForwardIterator> impl(new LinkedListForwardIterator(nullptr));
        return typename IQueue<T>::iterator(std::move(impl));
    }
};

Now in order to test that the iterators work I have the following 2 functions:

template <typename T>
void testQueueImpl(std::shared_ptr<IQueue<T> > queue) {
    queue->enqueue(1);
    queue->enqueue(2);
    queue->enqueue(3);
    queue->enqueue(4);
    queue->enqueue(5);
    queue->enqueue(6);

    std::cout << "Iterator behavior check 1st: ";
    for (auto e: *queue) {
        std::cout << e << " ";
    }
    std::cout << std::endl;

    std::cout << "Iterator behavior check 2nd: ";
    for (auto it = queue->begin(); it != queue->end(); it++) {
        std::cout << *it << " ";
    }
}

void testQueue() {
    auto queue = std::make_shared<LinkedListQueue<int> >();
    testQueueImpl<int>(queue);

    auto queue2 = std::make_shared<ResizingArrayQueue<int> >();
    testQueueImpl<int>(queue2);
}

Question

How can I get rid of the run-time polymorphism (remove IQueue, remove the iterator Pimpl implementations), and rewrite the testQueue() / testQueueImpl() functions so that:

  1. the functions can successfully test the Stack implementations and Stack iterators, without having a base class pointer.
  2. that both LinkedListQueue and ResizingArrayQueue adhere to some kind of a compile-time interface (the enqueue, dequeue, isEmpty, size methods are present, the begin / end methods are present, both classes contain valid iterator classes)?

Possible solution

For 1) it seems that I can simply change the template argument to be the whole container, and the program compiles successfully and runs. But this does not check for the existence of the begin() / end() / enqueue() methods.

For 2) from what I could find on the internet, it seems that the relevant solution would involve Type Traits / SFINAE / or Concepts (Container concept, forward iterator concept). It seems that Boost Concepts library allows annotating a class to conform to a container concept, but I am interested in a self-contained solution (no external libraries except STL) for educational purposes.

template <typename Container>
void testQueueImpl(Container queue) {
    queue->enqueue(1);
    queue->enqueue(2);
    queue->enqueue(3);
    queue->enqueue(4);
    queue->enqueue(5);
    queue->enqueue(6);

    std::cout << "Size: " << queue->size() << std::endl;

    std::cout << "Iterator behavior check 1st: ";
    for (auto e: *queue) {
        std::cout << e << " ";
    }
    std::cout << std::endl;

    std::cout << "Iterator behavior check 2nd: ";
    for (auto it = queue->begin(); it != queue->end(); it++) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
}

void testQueue() {
    auto queue = std::make_shared<LinkedListQueue<int> >();
    testQueueImpl<std::shared_ptr<LinkedListQueue<int> > >(queue);

    auto queue2 = std::make_shared<ResizingArrayQueue<int> >();
    testQueueImpl<std::shared_ptr<ResizingArrayQueue<int> > >(queue2);
}

Answers


Here is a minimum compilable example of how you might want to do it.

Note that at the moment, this example only supports const begin() and const end().

The addition of further methods and a mutable iterator is an exercise for the reader

EDIT: provided working example of both compile time and runtime polymorphic queues that shared the same policy classes.

#include <iostream>
#include <list>
#include <vector>
#include <memory>
#include <typeinfo>
#include <typeindex>

/// COMPILE TIME Polymorphic queue of objects of type Element

template<typename Element, class Policy>
struct queue_concept
{
    // Define interface
    struct const_iterator;
    void push_back(Element e);
    const_iterator begin() const;
    const_iterator end() const;


    // Implementation
private:
    Policy _policy;
};

// implement class methods an inner classes

template<typename Element, class Policy>
struct queue_concept<Element, Policy>::const_iterator
{
    using iterator_type = typename Policy::container_type::const_iterator;

    const_iterator(iterator_type iter = iterator_type {})
    : _iter { std::move(iter) }
    {}

    const Element& operator*() const {
        return *_iter;
    }

    const_iterator& operator++() {
        std::advance(_iter, 1);
    }

    bool operator!=(const const_iterator& other) const {
        return _iter != other._iter;
    }

    iterator_type _iter;
};

template<typename Element, class Policy>
void queue_concept<Element, Policy>::push_back(Element e)
{
    _policy._data.push_back(std::move(e));
}

template<typename Element, class Policy>
typename queue_concept<Element, Policy>::const_iterator queue_concept<Element, Policy>::begin() const
{
    return const_iterator { _policy._data.begin() };
}

template<typename Element, class Policy>
typename queue_concept<Element, Policy>::const_iterator queue_concept<Element, Policy>::end() const
{
    return const_iterator { _policy._data.end() };
}

/// RUNTIME Polymorphic queue of objects of type Element
template<typename Element>
struct IQueue
{
    struct const_iterator
    {
        struct Concept {
            // virtual base class so make destructor virtual...
            virtual ~Concept() = default;
            virtual const Element& get_element() const = 0;
            virtual void increment(std::size_t distance) = 0;
            bool equal_to(const Concept& rhs)
            {
                if (this->get_type() == rhs.get_type()) {
                    return unsafe_is_equal(rhs);
                }
                return false;
            }

            virtual bool unsafe_is_equal(const Concept& rhs) const = 0;
            virtual std::type_index get_type() const = 0;

            // provide copy support
            virtual std::unique_ptr<Concept> clone() const = 0;

        };

        template<class Iter>
        struct Model : public Concept {
            Model(Iter iter) : _iter { std::move(iter) }
            {}

            const Element& get_element() const override {
                return *_iter;
            }

            void increment(std::size_t distance) override {
                std::advance(_iter, distance);
            }

            bool unsafe_is_equal(const Concept& rhs) const override {
                auto _rhs = static_cast<const Model&>(rhs);
                return _iter == _rhs._iter;
            }

            std::type_index get_type() const override {
                return std::type_index(typeid(*this));
            }

            std::unique_ptr<Concept> clone() const override {
                return std::unique_ptr<Concept> { new Model(*this) };
            }

        private:
            Iter _iter;    
        };

        // constructor
        template<class Iter>
        const_iterator(Iter iter)
        : _impl { new Model<Iter> { std::move(iter) } }
        {}

        // default constructor - constructs an invalid iterator
        const_iterator()
        {}

        // provide copy support since impl is a unique_ptr
        const_iterator(const const_iterator& other)
        : _impl { other._impl ? other._impl->clone() : std::unique_ptr<Concept>{} }
        {}

        const_iterator& operator=(const_iterator& other)
        {
            auto p = other._impl ? other._impl->clone() : std::unique_ptr<Concept>{};
            std::swap(_impl, p);
        }

        // since we provided copy support we must provide move support
        const_iterator(const_iterator&& rhs) = default;
        const_iterator& operator=(const_iterator&& rhs) = default;

        const Element& operator*() const {
            return _impl->get_element();
        }
        const_iterator& operator++() {
            _impl->increment(1);
            return *this;
        }
        bool operator!=(const const_iterator& rhs) const
        {
            return !(_impl->equal_to(*(rhs._impl)));
        }

    private:
        std::unique_ptr<Concept> _impl;
    };


    virtual void push_back(Element e) = 0;
    virtual const_iterator begin() const = 0;
    virtual const_iterator end() const = 0;
};


template<class Element, class Policy>
struct QueueImpl : public IQueue<Element>
{
    void push_back(Element e) override {
        _policy._data.push_back(std::move(e));
    }

    typename IQueue<Element>::const_iterator begin() const override {
        return typename IQueue<Element>::const_iterator { std::begin(_policy._data) };
    }

    typename IQueue<Element>::const_iterator end() const override {
        return typename IQueue<Element>::const_iterator { std::end(_policy._data) };
    }


    Policy _policy;
};

template<class Element>
struct ResizingArrayPolicy
{
    using container_type = std::vector<Element>;
    container_type _data;
};

template<class Element>
struct LinkedListPolicy
{
    using container_type = std::list<Element>;
    container_type _data;
};

template<class Element>
std::unique_ptr<IQueue<Element>> make_poly_resizing_array_queue()
{
    return std::unique_ptr<IQueue<Element>> { new QueueImpl<Element, ResizingArrayPolicy<Element>> };
}

template<class Element>
std::unique_ptr<IQueue<Element>> make_poly_linked_list_queue()
{
    return std::unique_ptr<IQueue<Element>> { new QueueImpl<Element, LinkedListPolicy<Element>>{} };
}

template<class Element>
queue_concept<Element, ResizingArrayPolicy<Element>> make_static_resizing_array_queue()
{
    return queue_concept<Element, ResizingArrayPolicy<Element>>{};
}

template<class Element>
queue_concept<Element, LinkedListPolicy<Element>> make_static_linked_list_queue()
{
    return queue_concept<Element, LinkedListPolicy<Element>>{};
}

using namespace std;

int main()
{
    // create the queues
    auto pq1 = make_poly_resizing_array_queue<int>();
    auto pq2 = make_poly_linked_list_queue<int>();

    // put data in them    
    pq1->push_back(10);
    pq1->push_back(20);

    pq2->push_back(30);
    pq2->push_back(40);

    // prove that iterators are assignable and moveable
    IQueue<int>::const_iterator it;
    it = pq1->begin();
    cout << *it << endl; // should print 10
    auto i2 = pq2->begin();
    it = move(i2);
    cout << *it << endl; // should print 30

    // prove that queues are polymorphic

    auto queues = vector<unique_ptr<IQueue<int>>>{};
    queues.push_back(move(pq1));
    queues.push_back(move(pq2));

    // print the vector of queues
    for(const auto& queue_ptr : queues) {
        for(const auto& item : *queue_ptr) {
            cout << item << endl;
        }
        cout << endl;
    }

    // now the static versions
    auto q1 = make_static_resizing_array_queue<int>();
    auto q2 = make_static_linked_list_queue<int>();

    q1.push_back(10);
    q1.push_back(20);

    q2.push_back(30);
    q2.push_back(40);

    cout << "static queues\n";
    for(const auto& item : q1) {
        cout << item << endl;
    }
    cout << endl;    
    for(const auto& item : q2) {
        cout << item << endl;
    }

    return 0;
}

The question is not clear whether you actual need runtime polymorphism (of what, for example?)

An approach could be similiar to the one used by C++ containers: have a class that will manage the allocation/deallocation and construction/destruction of the objects.

template <typename T, class Allocator> 
class Queue
{
     Allocator myAllocator;

 public:
        void enqueue(T item) 
        {
            myAllocator.push(item);
        }

       // other operations.        

};

and then have something like

template <class T, template <typename ...> class Container, class ... Args>
class BasicAllocator
{
      Container<T, Args...> M_list;
public:     
      void push(T element)
      {
          M_list.push_back(element);
      }

      auto begin() -> decltype( std::begin(M_list) )
      { return std::begin(M_list); }

      auto end()   -> decltype( std::end(M_list) )
      { return std::end(M_list); }
};

template<class T>
using LinkedListAllocator = BasicAllocator<T, std::list>;

template<class T>
using LinkedListQueue = Queue<T, LinkedListAllocator<T>>;

Implementing dequeue might be a little trickier. Live example


Need Your Help

Fastest way fetch an array of memory values

c# c++ caching memory fetch

At the heart of an indexing structure, I find myself wondering if an optimization could be made for the following problem :

Does GroupView work in the TListView in OwnerData mode?

listview delphi delphi-xe2 tlistview

I'm trying to implement an "Arrange by" feature for a TListView in Delphi XE2. In the form designer (if I turn off OwnerData) I can get groups to show up and add items to them