1
\$\begingroup\$

I've developed a polymorphic container, that is itself a value-type, and also allocator-aware.
This makes it slightly heavy, with each node having both a cloner and a deleter. It does not put
any constrains on the target type, a clone method is not required. Instead, the container needs
to know each objects exact type, and uses the copy-constructor for cloning.

#include <cstddef>
#include <type_traits>
#include <iterator>
#include <vector>
#include <cassert>
#include <typeinfo>
#include <functional>

// ---------------------------------------------------------------------------------------------------------------------

namespace library {

template< typename, template<typename...> class, template<typename...> class >
class polymorphic_container;

namespace detail
{

    template<typename T>
    using clean = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

    template<typename,typename,typename>
    struct iterator;

    template<typename T,typename underlying_iterator>
    struct iterator_base
    {
        iterator_base() = default;
        iterator_base& operator++() { ++iter; return *this; }
        iterator_base& operator--() { --iter; return *this; }
        iterator_base operator++(int) { iterator_base tmp = *this; ++iter; return tmp; }
        iterator_base operator--(int) { iterator_base tmp = *this; --iter; return tmp; }
        bool operator==(const iterator_base& other) const { return iter==other.iter; }
        bool operator!=(const iterator_base& other) const { return iter!=other.iter; }
        T& operator*() const { return *ptr(); }
        T* operator->() const { return ptr(); };

    protected:
        underlying_iterator iter;
        iterator_base(underlying_iterator i) : iter(i) {}
    private:
        T* ptr() const { return (T*)iter->value; }

        template<typename,typename,typename>
        friend struct iterator;

    };

    template<typename T,typename underlying_iterator,typename category>
    struct iterator : std::iterator< category, T > , iterator_base<T,underlying_iterator>
    {
        iterator() = default;
        iterator( const iterator_base<T,underlying_iterator>& ib )
            : iterator_base<T,underlying_iterator>(ib.iter) {}

        using iterator_base<T,underlying_iterator>::iterator_base;

        template< typename, template<typename...> class, template<typename...> class >
        friend class library::polymorphic_container;

        // if the underlying iterator can convert, support it
        template<typename T2,typename UI2,typename C2>
        operator iterator<T2,UI2,C2>() { return {this->iter}; }

        template<typename U,typename UI2,typename C2>
        friend struct iterator;
    };

    template<typename T,typename underlying_iterator>
    struct iterator< T, underlying_iterator, std::random_access_iterator_tag >
        : std::iterator< std::random_access_iterator_tag, T > , iterator_base<T,underlying_iterator>
    {
        iterator() = default;
        iterator( const iterator_base<T,underlying_iterator>& ib )
            : iterator_base<T,underlying_iterator>(ib.iter) {}

        using iterator_base<T,underlying_iterator>::iterator_base;

        iterator& operator+=(std::ptrdiff_t diff) { this->iter+=diff; return *this; }
        iterator& operator-=(std::ptrdiff_t diff) { this->iter-=diff; return *this; }
        iterator operator+(std::ptrdiff_t diff) { auto tmp = *this; tmp+=diff; return tmp; }
        iterator operator-(std::ptrdiff_t diff) { auto tmp = *this; tmp-=diff; return tmp; }

        bool operator<(const iterator& other) const { return this->iter < other.iter; }
        bool operator>(const iterator& other) const { return this->iter > other.iter; }
        bool operator<=(const iterator& other) const { return this->iter <= other.iter; }
        bool operator>=(const iterator& other) const { return this->iter >= other.iter; }

        T& operator[](std::size_t idx) const { return *this->iter[idx].value; }

        std::ptrdiff_t operator-(const iterator& other) const { return this->iter - other.iter; }

        template< typename, template<typename...> class, template<typename...> class >
        friend class library::polymorphic_container;

        // if the underlying iterator can convert, support it
        template<typename T2,typename UI2,typename C2>
        operator iterator<T2,UI2,C2>() { return {this->iter}; }

        template<typename U,typename UI2,typename C2>
        friend struct iterator;
    };

}

// ---------------------------------------------------------------------------------------------------------------------

template<
    typename T,
    template<typename...> class Underlying = std::vector,
    template<typename...> class Allocator = std::allocator
>
class polymorphic_container
{
private:
    typedef std::function<T*(T*)> cloner_t;
    typedef std::function<void(T*)> deleter_t;
    struct Item
    {
        T* value = nullptr;
        cloner_t cloner = nullptr;
        deleter_t deleter = nullptr;

        Item() = default;
        Item( const Item& other ) : Item() { copy(other); }
        Item( Item&& other ) noexcept : Item() { swap(other); }
        Item& operator=( const Item& other ) { clear(); copy(other); return *this; }
        Item& operator=( Item&& other ) noexcept { clear(); swap(other); return *this; }
        ~Item() { clear(); }
        T& operator*() const;
        void swap(Item&) noexcept;
        void clear();

        bool operator <  (const Item& i) { return (*value) <  *i; }
        bool operator <= (const Item& i) { return (*value) <= *i; }
        bool operator >  (const Item& i) { return (*value) >  *i; }
        bool operator >= (const Item& i) { return (*value) >= *i; }
        bool operator == (const Item& i) { return (*value) == *i; }
        bool operator != (const Item& i) { return (*value) != *i; }

    private:
        void copy( const Item& );
    };

    template<typename U>
    cloner_t make_cloner();

    template<typename U>
    deleter_t make_deleter();

    template<typename U>
    static Allocator<U>& allocator()
    {
        static Allocator<U> a;
        return a;
    }

    typedef Underlying<Item,Allocator<Item>> underlying_container;
    typedef typename underlying_container::iterator underlying_iterator;
    typedef typename underlying_container::const_iterator underlying_const_iterator;
    typedef typename std::iterator_traits< underlying_iterator >::iterator_category underlying_iterator_category;

    template<typename U>
    struct sub_or_same
    {
        using CT = detail::clean<T>;
        using CU = detail::clean<U>;
        static const bool value =
            std::is_base_of < CT, CU >::value ||
            std::is_same    < CT, CU >::value ;
    };

    static const bool underlying_swap_noexcept =
        noexcept( std::declval<underlying_container&>() .swap( std::declval<underlying_container&>() ) );
public:

    typedef std::size_t size_type;
    typedef T value_type;
    typedef T& reference;
    typedef const T& const_reference;

    polymorphic_container() = default;
    polymorphic_container(const polymorphic_container&) = default;
    polymorphic_container(polymorphic_container&&) = default;

    polymorphic_container& operator=(const polymorphic_container&) = default;
    polymorphic_container& operator=(polymorphic_container&&) = default;

    ~polymorphic_container() = default;

    void assign( const polymorphic_container& other ) { data.assign(other.data); }
    void assign( polymorphic_container&& other ) noexcept(underlying_swap_noexcept) { data.swap(other.data); }

    void clear() { data.clear(); }
    void swap( polymorphic_container&  other ) noexcept(underlying_swap_noexcept) { data.swap(other.data); }
    void swap( polymorphic_container&& other ) noexcept(underlying_swap_noexcept) { data.swap(other.data); }

    size_type size() { return data.size(); }
    bool empty() { return data.empty(); }

    typedef detail::iterator<T,underlying_iterator,underlying_iterator_category> iterator;
    typedef detail::iterator<const T,underlying_const_iterator,underlying_iterator_category> const_iterator;

    iterator begin() { return { data.begin() }; }
    iterator end()   { return { data.end()   }; }
    const_iterator begin()  const { return { data.begin() }; }
    const_iterator end()    const { return { data.end()   }; }
    const_iterator cbegin() const { return { data.begin() }; }
    const_iterator cend()   const { return { data.end()   }; }

    typedef std::reverse_iterator<iterator> reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

    reverse_iterator rbegin() { return reverse_iterator{ end()   }; }
    reverse_iterator rend()   { return reverse_iterator{ begin() }; }
    const_reverse_iterator rbegin()  const { return const_reverse_iterator{ end()   }; }
    const_reverse_iterator rend()    const { return const_reverse_iterator{ begin() }; }
    const_reverse_iterator crbegin() const { return const_reverse_iterator{ end()   }; }
    const_reverse_iterator crend()   const { return const_reverse_iterator{ begin() }; }

private:
    template<typename U>
    iterator explicit_insert( const_iterator, const U& );

    template<typename U>
    iterator explicit_insert( const_iterator, U&& );

public:
    template<typename U, typename = std::enable_if_t<sub_or_same<U>::value>>
    iterator insert( const_iterator i, U&& u )
    {
        return explicit_insert< detail::clean<U> > ( i, std::forward<U>(u) );
    }

    // can take over ownership of pointers, but user must supply cloner & deleter
    iterator insert( const_iterator i, T* ptr, std::function<T*(T*)> cloner, std::function<void(T*)> deleter );

    template<typename U=T, typename... Args, typename = std::enable_if_t<sub_or_same<U>::value>>
    iterator emplace( const_iterator, Args&&... );

    iterator erase( const_iterator i ) { return { data.erase(i.iter) }; }

    template<typename U, typename = std::enable_if_t<sub_or_same<U>::value>>
    void push_back( U&& u )
    { insert( end(), std::forward<U>(u) ); }

    template<typename U=T, typename... Args, typename = std::enable_if_t<sub_or_same<U>::value>>
    void emplace_back( Args&&... args )
    { emplace<U>( end(), std::forward<Args>(args)... ); }

    void pop_back() { erase(end()); }

    T& back() { return *data.back(); }
    const T& back() const { return *data.back(); }

    T& front() { return *data.front(); }
    const T& front() const { return *data.front(); }

    T& operator[](std::size_t idx)
    {
        static_assert(
            std::is_same< underlying_iterator_category, std::random_access_iterator_tag >::value,
            "underlying container does not support random access"
        );
        return begin()[idx];
    }
    const T& operator[](std::size_t idx) const
    {
        static_assert(
            std::is_same< underlying_iterator_category, std::random_access_iterator_tag >::value,
            "underlying container does not support random access"
        );
        return begin()[idx];
    }

private:

    underlying_container data;

};

// ---------------------------------------------------------------------------------------------------------------------

template< typename T, template<typename...> class U, template<typename...> class A >
void swap(polymorphic_container<T,U,A>& lhs, polymorphic_container<T, U, A>& rhs)
{
    lhs.swap(rhs);
}

template< typename T, template<typename...> class U, template<typename...> class A >
bool operator == (polymorphic_container<T, U, A>& lhs, polymorphic_container<T, U, A>& rhs)
{
    auto li = lhs.begin();
    auto ri = rhs.begin();
    while(true)
    {
        bool le = li==lhs.end();
        bool re = ri==rhs.end();
        if(le&&re) return true;
        if(le||re) return false;
        if( *li != *ri ) return false;
        ++li; ++ri;
    }
}

template< typename T, template<typename...> class U, template<typename...> class A >
bool operator != (polymorphic_container<T, U, A>& lhs, polymorphic_container<T, U, A>& rhs)
{
    return ! (lhs==rhs);
}

template< typename T, template<typename...> class U, template<typename...> class A >
bool operator < (polymorphic_container<T, U, A>& lhs, polymorphic_container<T, U, A>& rhs)
{
    auto li = lhs.begin();
    auto ri = rhs.begin();
    while(true)
    {
        bool le = li==lhs.end();
        bool re = ri==rhs.end();
        if(le&&re) return false;
        if(le||re) return le;
        if( *li < *ri ) return true;
        if( *li > *ri ) return false;
        ++li; ++ri;
    }
}

template< typename T, template<typename...> class U, template<typename...> class A >
bool operator <= (polymorphic_container<T, U, A>& lhs, polymorphic_container<T, U, A>& rhs)
{
    auto li = lhs.begin();
    auto ri = rhs.begin();
    while(true)
    {
        bool le = li==lhs.end();
        bool re = ri==rhs.end();
        if(le&&re) return true;
        if(le||re) return le;
        if( *li < *ri ) return true;
        if( *li > *ri ) return false;
        ++li; ++ri;
    }
}

template< typename T, template<typename...> class U, template<typename...> class A >
bool operator > (polymorphic_container<T, U, A>& lhs, polymorphic_container<T, U, A>& rhs)
{
    return ! (lhs<=rhs);
}

template< typename T, template<typename...> class U, template<typename...> class A >
bool operator >= (polymorphic_container<T, U, A>& lhs, polymorphic_container<T, U, A>& rhs)
{
    return ! (lhs<rhs);
}

// ---------------------------------------------------------------------------------------------------------------------

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
void polymorphic_container<T,Underlying,Allocator>::Item::swap(Item& other) noexcept
{
    using std::swap;
    swap(value, other.value);
    swap(cloner, other.cloner);
    swap(deleter, other.deleter);
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
template<typename U>
auto polymorphic_container<T,Underlying,Allocator>::make_cloner() -> cloner_t
{
    auto cloner = [](T* t) -> T*
    {
        U* space = allocator<U>().allocate(1);
        U* item = (U*)t;
        U* new_ptr = new (space) U(*item);
        return (T*)new_ptr;
    };
    return cloner;
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
template<typename U>
auto polymorphic_container<T,Underlying,Allocator>::make_deleter() -> deleter_t
{
    auto deleter = [](T* t) -> void
    {
        U* item = (U*)t;
        allocator<U>().deallocate(item,1);
    };
    return deleter;
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
void polymorphic_container<T,Underlying,Allocator>::Item::clear()
{
    if(value)
    {
        assert(deleter);
        deleter(value);
    }
    value = nullptr;
    cloner = nullptr;
    deleter = nullptr;
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
void polymorphic_container<T,Underlying,Allocator>::Item::copy( const Item& other )
{
    if(other.value)
    {
        cloner = other.cloner;
        deleter = other.deleter;
        assert(cloner&&deleter);
        value = cloner(other.value);
    }
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
auto polymorphic_container<T,Underlying,Allocator>::Item::operator*() const -> T&
{
    assert(value);
    return *value;
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
template<typename U>
auto polymorphic_container<T,Underlying,Allocator>::explicit_insert( const_iterator i, const U& u ) -> iterator
{
    assert( (typeid(u) == typeid(U)) && "polymorphic_container need to know the exact type" );
    U* space = allocator<U>().allocate(1);
    U* value = new (space) U(u);
    auto iter = data.emplace(i.iter);
    iter->value = value;
    iter->cloner = make_cloner<U>();
    iter->deleter = make_deleter<U>();
    return {iter};
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
template<typename U>
auto polymorphic_container<T,Underlying,Allocator>::explicit_insert( const_iterator i, U&& u ) -> iterator
{
    assert( (typeid(u) == typeid(U)) && "polymorphic_container need to know the exact type" );
    U* space = allocator<U>().allocate(1);
    U* value = new (space) U(std::move(u));
    auto iter = data.emplace(i.iter);
    iter->value = value;
    iter->cloner = make_cloner<U>();
    iter->deleter = make_deleter<U>();
    return {iter};
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
auto polymorphic_container<T,Underlying,Allocator>::insert(
        const_iterator i, T* ptr, std::function<T*(T*)> cloner, std::function<void(T*)> deleter
    ) -> iterator
{
    auto ui = data.emplace(i.iter);
    ui->value = ptr;
    ui->cloner = std::move(cloner);
    ui->deleter = std::move(deleter);
    return {ui};
}

template< typename T, template<typename...> class Underlying, template<typename...> class Allocator >
template<typename U, typename... Args, typename >
auto polymorphic_container<T,Underlying,Allocator>::emplace( const_iterator i, Args&&... args ) -> iterator
{
    U* space = allocator<U>().allocate(1);
    U* value = new (space) U( std::forward<Args>(args)... );
    auto iter = data.emplace(i.iter);
    iter->value = value;
    iter->cloner = make_cloner<U>();
    iter->deleter = make_deleter<U>();
    return {iter};
}

}

The file can also be found here

\$\endgroup\$
  • \$\begingroup\$ Also see my solution (adding polymorphic behavior over the contained base class type, not the entire container). \$\endgroup\$ – utnapistim Nov 6 '15 at 12:21
  • \$\begingroup\$ Can you provide example usage? \$\endgroup\$ – Barry Nov 6 '15 at 15:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.