tuple.h

Back to Tuples

pastel/sys/tuple/

// Description: Tuple

#ifndef PASTELSYS_TUPLE_H
#define PASTELSYS_TUPLE_H

#include "pastel/sys/mytypes.h"
#include "pastel/sys/ensure.h"
#include "pastel/sys/destruct.h"

#include <vector>
#include <algorithm>
#include <memory>

#include <boost/operators.hpp>

namespace Pastel
{

    template <integer N, integer NewN>
    class ModifyN
    {
    public:
        static constexpr int Result =
            (N == Dynamic) ? Dynamic : NewN;
    };

    class Dimension
    {
    public:
        explicit Dimension(integer dimension)
            : dimension_(dimension)
        {
            PENSURE_OP(dimension, >=, 0);
        }

        operator integer() const
        {
            return dimension_;
        }

    private:
        Dimension() = delete;

        integer dimension_;
    };

    inline std::string asString(Dimension n)
    {
        using Pastel::asString;
        return asString((integer)n);
    }

    template <typename Type>
    class Alias
    {
    public:
        explicit Alias(Type data)
            : data_(data)
        {
        }

        operator Type() const
        {
            return data_;
        }

    private:
        Alias() = delete;

        Type data_;
    };

    template <typename Type>
    class Copy
    {
    public:
        explicit Copy(Type data)
            : data_(data)
        {
        }

        operator Type() const
        {
            return data_;
        }

    private:
        Copy() = delete;

        Type data_;
    };

    inline Dimension ofDimension(integer dimension)
    {
        return Dimension(dimension);
    }

    template <typename Type>
    inline Alias<Type*> withAliasing(Type* data)
    {
        return Alias<Type*>(data);
    }

    template <typename Type, integer N = Dynamic>
    class Tuple
        : boost::equality_comparable<Tuple<Type, N> >
    {
    public:
        template <typename, integer>
        friend class Tuple;

        using value_type = Type;
        using iterator = Type*;
        using const_iterator = const Type*;
        using reverse_iterator = Type*;
        using const_reverse_iterator = const Type*;
        using pointer = Type*;
        using const_pointer = const Type*;
        using reference = Type&;
        using const_reference = const Type&;
        using difference_type = integer;
        using size_type = integer;

        using Iterator = iterator;
        using ConstIterator = const_iterator;

        Tuple()
            : data_()
        {
        }

        explicit Tuple(const Type& that)
            : data_()
        {
            set(that);
        }

        explicit Tuple(const Type* that)
            : data_()
        {
            std::copy(that, that + N, data_);
        }

        explicit Tuple(
            const Dimension& dimension,
            const Type& that = Type())
            : data_()
        {
            PENSURE_OP(dimension, ==, N);

            set(that);
        }

        Tuple(
            const Dimension& dimension,
            const Copy<const Type*>& that)
            : data_()
        {
            PENSURE_OP(dimension, ==, N);
            std::copy((const Type*)that, (const Type*)that + N, data_);
        }

        // Alias for static tuple is copying.
        // This is needed so that one does not have to
        // make special cases in generic programming.
        Tuple(
            const Dimension& dimension,
            const Alias<Type*>& dataAlias)
            : Tuple(dimension, Copy<const Type*>(dataAlias))
        {
        }

        // Using default copy constructor.
        // Maybe this is more efficient than doing
        // what's below, since 'data_' need not
        // be default initialized.
        Tuple(const Tuple& that) = default;
        /*
       Tuple(const Tuple& that)
           : data_()
       {
           std::copy(that.begin(), that.end(), begin());
       }
       */

        // Note copy constructor won't match this function.
        template <typename ThatType, integer ThatN>
        Tuple(const Tuple<ThatType, ThatN>& that)
            : data_()
        {
            PENSURE_OP(size(), ==, that.size());
            std::copy(that.begin(), that.end(), begin());
        }

        template <
            integer N_ = N,
            RequiresC<(N_ == 2)> = 0>
        Tuple(const Type& x, const Type& y)
        {
            set(x, y);
        }

        template <
            integer N_ = N,
            RequiresC<(N_ == 3)> = 0>
        Tuple(const Type& x, const Type& y, const Type& z)
        {
            set(x, y, z);
        }

        template <
            integer N_ = N,
            RequiresC<(N_ == 4)> = 0>
        Tuple(const Type& x, const Type& y, 
            const Type& z, const Type& w)
        {
            set(x, y, z, w);
        }

        // Using default operator=.

        ~Tuple()
        {
            PASTEL_STATIC_ASSERT(N == Dynamic || N > 0);

            static constexpr bool IsBase =
                std::is_base_of<Tuple, Tuple<Type, N> >::value;

            PASTEL_STATIC_ASSERT(IsBase);
        }

        // The assignment need not be implemented, since
        // we settle for basic exception safety rather than strong
        // for performance (no element swapping).
        /*
       Tuple<Type, N>& operator=(const Tuple& that)
       {
           std::copy(that.begin(), that.end(), begin());
           
           return *this;
       }
       */

        Tuple<Type, N>& operator=(
            const std::initializer_list<Type>& that)
        {
            integer n = std::min(size(), (integer)that.size());
            std::copy_n(that.begin(), n, begin());
            return *this;
        }

        void swap(Tuple<Type, N> & that)
        {
            using std::swap;

            for (integer i = 0;i < N;++i)
            {
                swap(data_[i], that.data_[i]);
            }
        }

        void setSize(integer size, const Type& that = Type())
        {
            ENSURE2(size >= 0 && size <= N, size, N);
            // Do nothing.
        }

        void set(const Type& that)
        {
            std::fill(begin(), end(), that);
        }

        Type& front()
        {
            return data_[0];
        }

        const Type& front() const
        {
            return data_[0];
        }

        Type& back()
        {
            return data_[N - 1];
        }

        const Type& back() const
        {
            return data_[N - 1];
        }

        Type* begin()
        {
            return &data_[0];
        }

        const Type* begin() const
        {
            return &data_[0];
        }

        Type* end()
        {
            return &data_[0] + N;
        }

        const Type* end() const
        {
            return &data_[0] + N;
        }

        size_type capacity() const
        {
            return N;
        }

        size_type size() const
        {
            return N;
        }

        size_type n() const
        {
            return N;
        }

        size_type max_size() const
        {
            return N;
        }

        bool empty() const
        {
            return false;
        }

        Type& at(integer index)
        {
            PENSURE2(index >= 0 && index < size(), index, size());

            return data_[index];
        }

        const Type& at(integer index) const
        {
            PENSURE2(index >= 0 && index < size(), index, size());

            return data_[index];
        }

        Type& operator[](integer index)
        {
            PENSURE2(index >= 0 && index < size(), index, size());
            return data_[index];
        }

        const Type& operator[](integer index) const
        {
            PENSURE2(index >= 0 && index < size(), index, size());

            return data_[index];
        }

        //! Returns the address of the first element.
        Type* rawBegin()
        {
            return data_;
        }

        //! Returns the address of the first element.
        const Type* rawBegin() const
        {
            return data_;
        }

        //! Returns the address of the one-past last element.
        Type* rawEnd()
        {
            return data_ + N;
        }

        //! Returns the address of the one-past last element.
        const Type* rawEnd() const
        {
            return data_ + N;
        }

        bool operator==(const Tuple<Type, N> & that) const
        {
            return std::equal(
                begin(), end(), that.begin());
        }

        template <
            integer N_ = N,
            RequiresC<(N_ == 2)> = 0>
        void set(
            const Type& x, const Type& y)
        {
            Tuple& v = *this;
            v[0] = x;
            v[1] = y;
        }

        template <
            integer N_ = N,
            RequiresC<(N_ == 3)> = 0>
        void set(
            const Type& x, const Type& y, 
            const Type& z)
        {
            Tuple& v = *this;
            v[0] = x;
            v[1] = y;
            v[2] = z;
        }

        template <
            integer N_ = N,
            RequiresC<(N_ == 4)> = 0>
        void set(
            const Type& x, const Type& y, 
            const Type& z, const Type& w)
        {
            Tuple& v = *this;
            v[0] = x;
            v[1] = y;
            v[2] = z;
            v[3] = w;
        }

        template <
            integer N_ = N,
            RequiresC<(N_ >= 1)> = 0>
        Type& x()
        {
            return (*this)[0];
        }

        template <
            integer N_ = N,
            RequiresC<(N_ >= 1)> = 0>
        const Type& x() const
        {
            return (*this)[0];
        }

        template <
            integer N_ = N,
            RequiresC<(N_ >= 2)> = 0>
        Type& y()
        {
            return (*this)[1];
        }

        template <
            integer N_ = N,
            RequiresC<(N_ >= 2)> = 0>
        const Type& y() const
        {
            return (*this)[1];
        }

        template <
            integer N_ = N,
            RequiresC<(N_ >= 3)> = 0>
        Type& z()
        {
            return (*this)[2];
        }

        template <
            integer N_ = N,
            RequiresC<(N_ >= 3)> = 0>
        const Type& z() const
        {
            return (*this)[2];
        }

        template <
            integer N_ = N,
            RequiresC<(N_ >= 4)> = 0>
        Type& w()
        {
            return (*this)[3];
        }

        template <
            integer N_ = N,
            RequiresC<(N_ >= 4)> = 0>
        const Type& w() const
        {
            return (*this)[3];
        }

    private:
        Type data_[N];
    };

    template <typename Type>
    class Tuple<Type, Dynamic>
        : boost::equality_comparable<Tuple<Type, Dynamic> >
    {
    private:
        static constexpr integer N = Dynamic;

    public:
        template <typename, integer>
        friend class Tuple;

        using value_type = Type;
        using iterator = Type*;
        using const_iterator = Type const*;
        using reverse_iterator = std::reverse_iterator<iterator>;
        using const_reverse_iterator = std::reverse_iterator<const_iterator>;
        using pointer = Type*;
        using const_pointer = const Type*;
        using reference = Type&;
        using const_reference = const Type&;
        using difference_type = integer;
        using size_type = integer;
        using Iterator = iterator;
        using ConstIterator = const_iterator;

        explicit Tuple(
            const Dimension& dimension,
            const Type& that = Type())
            : data_(0)
            , size_(0)
            , deleteData_(true)
        {
            integer size = dimension;
            allocate(size);

            try
            {
                std::uninitialized_fill_n(data_, size, that);
            }
            catch(...)
            {
                deallocate();
                throw;
            };
        }

        Tuple(
            const Tuple& that)
            : data_(0)
            , size_(0)
            , deleteData_(true)
        {
            allocate(that.size());

            try
            {
                copyConstruct(that);
            }
            catch(...)
            {
                deallocate();
                throw;
            };
        }

        Tuple(
            const Tuple& that,
            const Dimension& dimension,
            const Type& defaultData = Type())
            : data_(0)
            , size_(0)
            , deleteData_(true)
        {
            integer size = dimension;
            allocate(size);

            try
            {
                copyConstruct(that, ofDimension(size), defaultData);
            }
            catch(...)
            {
                deallocate();
                throw;
            };
        }

        template <typename ThatType, integer ThatN>
        Tuple(
            const Tuple<ThatType, ThatN>& that)
            : data_(0)
            , size_(0)
            , deleteData_(true)
        {
            allocate(that.size());

            try
            {
                copyConstruct(that);
            }
            catch(...)
            {
                deallocate();
                throw;
            };
        }

        template <typename ThatType, integer ThatN>
        Tuple(
            const Tuple<ThatType, ThatN>& that,
            const Dimension& dimension,
            const Type& defaultData = Type())
            : data_(0)
            , size_(0)
            , deleteData_(true)
        {
            integer size = dimension;
            allocate(size);

            try
            {
                copyConstruct(that, size, defaultData);
            }
            catch(...)
            {
                deallocate();
                throw;
            };
        }

        Tuple(
            const Dimension& dimension,
            const Alias<Type*>& dataAlias)
            : data_(dataAlias)
            , size_(dimension)
            , deleteData_(false)
        {
        }

        Tuple(
            const Dimension& dimension,
            const Copy<const Type*>& that)
            : data_(0)
            , size_(0)
            , deleteData_(true)
        {
            integer size = dimension;
            allocate(size);

            std::copy((const Type*)that, (const Type*)that + size, data_);
        }

        ~Tuple()
        {
            if (deleteData_)
            {
                destruct(data_, data_ + size_);
                deallocate();
            }

            static constexpr bool IsBase = std::is_base_of<Tuple, Tuple<Type, N> >::value;

            PASTEL_STATIC_ASSERT(IsBase);
        }

        Tuple<Type, N>& operator=(const Tuple& that)
        {
            // We settle for basic exception safety rather than strong
            // for performance (no memory reallocation).
            PENSURE_OP(size_, ==, that.size_);

            std::copy(that.begin(), that.end(), begin());

            return *this;
        }

        Tuple<Type, N>& operator=(
            const std::initializer_list<Type>& that)
        {
            integer n = std::min(size(), (integer)that.size());
            std::copy_n(that.begin(), n, begin());
            return *this;
        }

        void setSize(integer size, const Type& that = Type())
        {
            ENSURE_OP(size, >=, 0);

            resize(size, that);
        }

        void resize(integer size, const Type& that = Type())
        {
            ENSURE_OP(size, >=, 0);

            Tuple copy(*this, ofDimension(size), that);
            swap(copy);
        }

        void swap(Tuple& that)
        {
            std::swap(data_, that.data_);
            std::swap(size_, that.size_);
            std::swap(deleteData_, that.deleteData_);
        }

        void set(const Type& that)
        {
            std::fill(begin(), end(), that);
        }

        Type& front()
        {
            PENSURE(!empty());
            return *data_;
        }

        const Type& front() const
        {
            PENSURE(!empty());
            return *data_;
        }

        Type& back()
        {
            PENSURE(!empty());
            return *(data_ + size_ - 1);
        }

        const Type& back() const
        {
            PENSURE(!empty());
            return *(data_ + size_ - 1);
        }

        iterator begin()
        {
            return data_;
        }

        const_iterator begin() const
        {
            return data_;
        }

        iterator end()
        {
            return data_ + size_;
        }

        const_iterator end() const
        {
            return data_ + size_;
        }

        size_type capacity() const
        {
            return size_;
        }

        size_type size() const
        {
            return size_;
        }

        size_type n() const
        {
            return size_;
        }

        size_type max_size() const
        {
            return size_;
        }

        bool empty() const
        {
            return size_ == 0;
        }

        Type& at(integer index)
        {
            PENSURE2(index >= 0 && index < size(), index, size());

            return data_[index];
        }

        const Type& at(integer index) const
        {
            PENSURE2(index >= 0 && index < size(), index, size());

            return data_[index];
        }

        Type& operator[](integer index)
        {
            PENSURE2(index >= 0 && index < size(), index, size());
            return data_[index];
        }

        const Type& operator[](integer index) const
        {
            PENSURE2(index >= 0 && index < size(), index, size());

            return data_[index];
        }

        //! Returns the address of the first element.
        Type* rawBegin()
        {
            return data_;
        }

        //! Returns the address of the first element.
        const Type* rawBegin() const
        {
            return data_;
        }

        //! Returns the address of the one-past last element.
        Type* rawEnd()
        {
            return data_ + size_;
        }

        //! Returns the address of the one-past last element.
        const Type* rawEnd() const
        {
            return data_ + size_;
        }

        bool operator==(const Tuple<Type, N> & that) const
        {
            PENSURE2(size() == that.size(), size(), that.size());

            return std::equal(
                begin(), end(), that.begin());
        }

    private:
        void allocate(integer size)
        {
            PENSURE_OP(size, >=, 0);
            ASSERT(data_ == 0);
            ASSERT1(size_ == 0, size_);

            data_ = (Type*)allocateRaw(sizeof(Type) * size);
            size_ = size;
        }

        void deallocate()
        {
            if (data_)
            {
                deallocateRaw((void*)data_);
            }
            data_ = 0;
            size_ = 0;
        }

        template <typename ThatType, integer ThatN>
        void copyConstruct(
            const Tuple<ThatType, ThatN>& that)
        {
            integer size = that.size();
            ASSERT_OP(size, ==, size_);

            try
            {
                std::uninitialized_copy(
                    that.data_, 
                    that.data_ + size, 
                    data_);
            }
            catch(...)
            {
                deallocate();
                throw;
            };
        }

        template <typename ThatType, integer ThatN>
        void copyConstruct(
            const Tuple<ThatType, ThatN>& that,
            const Dimension& dimension,
            const Type& defaultData)
        {
            integer size = dimension;

            integer minSize = std::min(
                that.size(), size);

            integer rollBackIndex = 0;
            try
            {
                std::uninitialized_copy(
                    that.data_, 
                    that.data_ + minSize, 
                    data_);
                ++rollBackIndex;

                if (size > minSize)
                {
                    std::uninitialized_fill_n(
                        data_ + minSize,
                        size - minSize,
                        defaultData);
                }
            }
            catch(...)
            {
                switch(rollBackIndex)
                {
                case 1:
                    destruct(data_, data_ + minSize);
                    // Fall-through.
                case 0:
                    deallocate();
                    break;
                };
                throw;
            };
        }

        Tuple() = delete;

        Type* data_;
        integer size_;
        bool deleteData_;
    };

}

namespace Pastel
{

    using Integer1 = Tuple<integer, 1>;
    using Type1 = Tuple<real, 1>;

    using Integer2 = Tuple<integer, 2>;
    using Type2 = Tuple<real, 2>;

    using Integer3 = Tuple<integer, 3>;
    using Type3 = Tuple<real, 3>;

    using Integer4 = Tuple<integer, 4>;
    using Type4 = Tuple<real, 4>;

    using IntegerD = Tuple<integer, Dynamic>;
    using TypeD = Tuple<real, Dynamic>;

}

#include "pastel/sys/tuple/tuple_tools.h"

#endif