    // Copyright (C) 2007  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_STD_ALLOc_H_
    #define DLIB_STD_ALLOc_H_

    #include <limits>
    #include <memory>
    #include "enable_if.h"
    #include "algs.h"

    namespace dlib
    {

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

        template <
            typename T,
            typename M
            >
        class std_allocator
        {
            /*!
                REQUIREMENTS ON M
                    must be an implementation of memory_manager/memory_manager_kernel_abstract.h or
                    must be an implementation of memory_manager_global/memory_manager_global_kernel_abstract.h or
                    must be an implementation of memory_manager_stateless/memory_manager_stateless_kernel_abstract.h
                    M::type can be set to anything.

                WHAT THIS OBJECT REPRESENTS
                    This object is an implementation of an allocator that conforms to the C++ standard
                    requirements for allocator objects.  The M template argument is one of the dlib
                    memory manager objects and this allocator implementation will do all of its memory allocations
                    using whatever dlib memory manager you supply.

                    Thus, using this allocator object you can use any of the dlib memory manager objects with
                    the containers in the STL or with any other object that requires a C++ allocator object.

                    It is important to note that many STL implementations make the assumption that the memory
                    allocated by one allocator can be freed by another.  This effectively means that you should
                    only use a global or stateless memory manager with the std_allocator.  Either that or you
                    have to verify that your version of the STL isn't going to try and allocate and deallocate
                    memory with different allocators.
            !*/

        public:
            //type definitions
            typedef std::size_t     size_type;
            typedef std::ptrdiff_t  difference_type;
            typedef T*              pointer;
            typedef const T*        const_pointer;
            typedef T&              reference;
            typedef const T&        const_reference;
            typedef T               value_type;

            //rebind std_allocator to type U
            template <typename U>
            struct rebind {
                typedef std_allocator<U,M> other;
            };

            //return address of values
            pointer address (reference value) const { return &value; }

            const_pointer address (const_reference value) const { return &value; }

            /*constructors and destructor
             *-nothing to do because the std_allocator has no state
            */
            std_allocator() throw() { }

            std_allocator(const std_allocator&) throw() { }

            template <typename U>
            std_allocator (const std_allocator<U,M>&) throw() { }

            ~std_allocator() throw() { }

            //return maximum number of elements that can be allocated
            size_type max_size () const throw()
            {
                //for numeric_limits see Section 4.3, page 59
                return std::numeric_limits<size_t>::max() / sizeof(T);
            }

            //allocate but don't initialize num elements of type T
            pointer allocate (
                size_type num,
                typename std_allocator<void,M>::const_pointer  = 0
            )
            {
                return (pointer) pool.allocate_array(num*sizeof(T));
            }

            // This function is not required by the C++ standard but some versions of the STL
            // distributed with gcc erroneously require it.  See the bug report for further
            // details: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=51626
            void construct(pointer p) { return construct(p, value_type()); }

            //initialize elements of allocated storage p with value value
            void construct (pointer p, const T& value)
            {
                //initialize memory with placement new
                new((void*)p)T(value);
            }

            //destroy elements of initialized storage p
            void destroy (pointer p)
            {
                // destroy objects by calling their destructor
                p->~T();
            }

            //deallocate storage p of deleted elements
            void deallocate (pointer p, size_type )
            {
                pool.deallocate_array((char*)p);
            }

            void swap (
                std_allocator& item
            )
            {
                pool.swap(item.pool);
            }

            std_allocator& operator= (const std_allocator&) { return *this;}

        private:
            typename M::template rebind<char>::other pool;
        };

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

        template <
            typename M
            >
        class std_allocator<void,M>
        {
        public:
            //type definitions
            typedef std::size_t     size_type;
            typedef std::ptrdiff_t  difference_type;
            typedef void*              pointer;
            typedef const void*        const_pointer;
            typedef void               value_type;

            //rebind std_allocator to type U
            template <typename U>
            struct rebind {
                typedef std_allocator<U,M> other;
            };

        };

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

        template <typename M1, typename M2, typename enabled = void>
        struct std_alloc_compare
        { const static bool are_interchangeable = false; };

        template <typename M1, typename M2>
        struct std_alloc_compare<M1,M2,typename enable_if<is_same_type<typename M1::mm_global_type, typename M2::mm_global_type> >::type>
        { const static bool are_interchangeable = true; };

        template <typename M>
    	struct std_alloc_compare<M,M,typename enable_if_c<M::is_stateless>::type>
        { const static bool are_interchangeable = true; };

        //return that all specializations of this std_allocator are interchangeable if they use memory_manager_global
        // instances with the same mm_global_type
        template <typename T1, typename M1, typename T2, typename M2>
        bool operator== (
            const std_allocator<T1,M1>&,
            const std_allocator<T2,M2>&
        ) throw()
        { return std_alloc_compare<M1,M2>::are_interchangeable; }

        template <typename T1, typename M1, typename T2, typename M2>
        bool operator!= (
            const std_allocator<T1,M1>&,
            const std_allocator<T2,M2>&
        ) throw()
        { return !std_alloc_compare<M1,M2>::are_interchangeable; }

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

        template <typename T, typename M>
        void swap (
            std_allocator<T,M>& a,
            std_allocator<T,M>& b
        ) { a.swap(b); }

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

    }

    #endif // DLIB_STD_ALLOc_H_