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

    #include "../enable_if.h"
    #include "../algs.h"
    #include "../serialize.h"
    #include "tuple_abstract.h"

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

    #define DLIB_TUPLE_GLOBAL_HELPERS(N)                                                \
        DLIB_TUPLE_GH(N##0) DLIB_TUPLE_GH(N##1) DLIB_TUPLE_GH(N##2) DLIB_TUPLE_GH(N##3) \
        DLIB_TUPLE_GH(N##4) DLIB_TUPLE_GH(N##5) DLIB_TUPLE_GH(N##6) DLIB_TUPLE_GH(N##7)

    #define DLIB_TUPLE_GH(N)  DLIB_TUPLE_GET_INDEX(N)  DLIB_TUPLE_GET_ITEM(N) DLIB_TUPLE_GET_HELPER_STRUCT(N)

    #define DLIB_TUPLE_MEMBER_GET(N)                                                    \
        DLIB_TUPLE_MG(N##0) DLIB_TUPLE_MG(N##1) DLIB_TUPLE_MG(N##2) DLIB_TUPLE_MG(N##3) \
        DLIB_TUPLE_MG(N##4) DLIB_TUPLE_MG(N##5) DLIB_TUPLE_MG(N##6) DLIB_TUPLE_MG(N##7)

    #define DLIB_TUPLE_GET_INDEX(N) \
        template <class Q, class T> const typename enable_if<is_same_type<typename T::type##N,Q>, long>::type get_index (const T&) {return N;}

    #define DLIB_TUPLE_GET_ITEM(N) \
        template <class Q,class T> const typename enable_if<is_same_type<typename T::type##N,Q>,Q>::type& get_item_const (const T& t) {return t.v##N;}\
        template <class Q,class T>       typename enable_if<is_same_type<typename T::type##N,Q>,Q>::type& get_item (      T& t) {return t.v##N;}

    #define DLIB_TUPLE_GET_HELPER_STRUCT(N)                         \
        template <class T> struct get_helper<N,T>                   \
        {                                                           \
            typedef typename T::type##N type;                       \
            static const type& get(const T& t) { return t.v##N; }   \
            static       type& get( T& t) { return t.v##N; }        \
        };

    #define DLIB_TUPLE_TEMPLATE_LIST(N) \
        class T##N##0 = null_type, class T##N##1 = null_type, class T##N##2 = null_type, class T##N##3 = null_type, \
        class T##N##4 = null_type, class T##N##5 = null_type, class T##N##6 = null_type, class T##N##7 = null_type

    #define DLIB_TUPLE_VARIABLE_LIST(N) \
        T##N##0 v##N##0; T##N##1 v##N##1; T##N##2 v##N##2; T##N##3 v##N##3; \
        T##N##4 v##N##4; T##N##5 v##N##5; T##N##6 v##N##6; T##N##7 v##N##7;  \
        typedef T##N##0 type##N##0; typedef T##N##1 type##N##1; typedef T##N##2 type##N##2;  \
        typedef T##N##3 type##N##3; typedef T##N##4 type##N##4; typedef T##N##5 type##N##5;  \
        typedef T##N##6 type##N##6; typedef T##N##7 type##N##7;

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

    namespace dlib
    {

        struct null_type{};

        // provide default serialization for the null_type
        inline void serialize (
            const null_type& ,
            std::ostream&
        ){}
        inline void deserialize (
            null_type& ,
            std::istream&
        ){}

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

        namespace tuple_helpers
        {
            template <long idx, class T> struct get_helper;

            // use these preprocessor macros to declare all the global stuff used by the
            // tuple member functions.
            DLIB_TUPLE_GLOBAL_HELPERS(0)
            DLIB_TUPLE_GLOBAL_HELPERS(01)
            DLIB_TUPLE_GLOBAL_HELPERS(02)
            DLIB_TUPLE_GLOBAL_HELPERS(03)

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

        // use templates to recursively enumerate everything in the tuple that isn't a null_type
            template <
                typename T,
                typename F,
                long i = 0,
                typename enabled = void
                >
            struct for_each
            {
                static void go(
                    T& a,
                    F& funct
                )
                {
                    funct(a.template get<i>());
                    for_each<T,F,i+1>::go(a,funct);
                }

                static bool go(
                    T& a,
                    F& funct,
                    long idx
                )
                /*!
                    ensures
                        - returns true if the function was applied to the given index
                        - returns false if the index is invalid so the function wasn't
                          applied to anything
                !*/
                {
                    if (idx == i)
                    {
                        funct(a.template get<i>());
                        return true;
                    }
                    else
                    {
                        return for_each<T,F,i+1>::go(a,funct,idx);
                    }
                }
            };

            template <bool v1, bool v2> struct template_or { const static bool value = true; };
            template <> struct template_or<false,false> { const static bool value = false; };

            // the base case of the recursion
            template <
                typename T,
                typename F,
                long i
                >
            struct for_each<T,F,i,typename enable_if<template_or<i == T::max_fields , is_same_type<null_type,typename T::template get_type<i>::type >::value> >::type >
            {
                static void go( T&, F& ) { }
                static bool go( T&, F&, long ) { return false; }
            };

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

        // use templates to recursively enumerate everything in the tuple that isn't a null_type
            template <
                typename T,
                long i = 0,
                typename enabled = void
                >
            struct tuple_swap
            {
                static void go(
                    T& a,
                    T& b
                )
                {
                    exchange(a.template get<i>(), b.template get<i>());
                    tuple_swap<T,i+1>::go(a,b);
                }
            };

            template <typename T, long i>
            struct at_base_case
            {

            };

            // the base case of the recursion
            template <
                typename T,
                long i
                >
            struct tuple_swap<T,i,typename enable_if<template_or<i == T::max_fields, is_same_type<null_type,typename T::template get_type<i>::type >::value > >::type >
            { static void go( T&, T& ) { } };

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

            struct tuple_serialize
            {
                tuple_serialize (std::ostream& out_) : out(out_){}
                std::ostream& out;

                template <typename T>
                void operator() (
                    T& a
                ) const { serialize(a,out); }
            };

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

            struct tuple_deserialize
            {
                tuple_deserialize (std::istream& in_) : in(in_){}
                std::istream& in;
                template <typename T>
                void operator() (
                    T& a
                ) const { deserialize(a,in); }
            };

        }

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

        // use these preprocessor macros to declare 4*8 template arguments (below we count them in octal)
        template <
            DLIB_TUPLE_TEMPLATE_LIST(0),  // args 00-07
            DLIB_TUPLE_TEMPLATE_LIST(01), // args 010-017
            DLIB_TUPLE_TEMPLATE_LIST(02), // args 020-027
            DLIB_TUPLE_TEMPLATE_LIST(03)  // args 030-037
            >
        class tuple
        {
        public:

            // use these macros to declare 8*4 member variables
            DLIB_TUPLE_VARIABLE_LIST(0)
            DLIB_TUPLE_VARIABLE_LIST(01)
            DLIB_TUPLE_VARIABLE_LIST(02)
            DLIB_TUPLE_VARIABLE_LIST(03)

            const static long max_fields = 4*8;

            template < long idx >
            struct get_type
            {
                typedef typename tuple_helpers::get_helper<idx,tuple>::type type;
            };

            template < long idx >
            const typename tuple_helpers::get_helper<idx,tuple>::type& get (
            ) const { return tuple_helpers::get_helper<idx,tuple>::get(*this); }

            template < long idx >
            typename tuple_helpers::get_helper<idx,tuple>::type& get (
            ) { return tuple_helpers::get_helper<idx,tuple>::get(*this); }

            template < class Q>
            long index (
            ) const { return tuple_helpers::get_index<Q>(*this); }

            template <class Q>
            const Q& get (
            ) const {return tuple_helpers::get_item_const<Q>(*this);}

            template <class Q>
            Q& get (
            ) {return tuple_helpers::get_item<Q>(*this);}

            template <typename F>
            void for_index (
                F& funct,
                long idx
            )
            {
                // do this #ifdef stuff to avoid getting a warning about valid_idx not being
                // used when ENABLE_ASSERTS isn't defined.
    #ifdef ENABLE_ASSERTS
                const bool valid_idx = tuple_helpers::for_each<tuple,F>::go(*this,funct,idx);
    #else
                tuple_helpers::for_each<tuple,F>::go(*this,funct,idx);
    #endif
                DLIB_ASSERT(valid_idx,
                    "\tvoid tuple::for_index()"
                    << "\n\tYou have attempted to call for_index() with an index out of the valid range"
                    << "\n\tidx:  " << idx
                    << "\n\tthis: " << this
                    );
            }

            template <typename F>
            void for_index (
                F& funct,
                long idx
            ) const
            {
                // do this #ifdef stuff to avoid getting a warning about valid_idx not being
                // used when ENABLE_ASSERTS isn't defined.
    #ifdef ENABLE_ASSERTS
                const bool valid_idx = tuple_helpers::for_each<const tuple,F>::go(*this,funct,idx);
    #else
                tuple_helpers::for_each<const tuple,F>::go(*this,funct,idx);
    #endif
                DLIB_ASSERT(valid_idx,
                    "\tvoid tuple::for_index()"
                    << "\n\tYou have attempted to call for_index() with an index out of the valid range"
                    << "\n\tidx:  " << idx
                    << "\n\tthis: " << this
                    );
            }

            template <typename F>
            void for_index (
                const F& funct,
                long idx
            )
            {
                // do this #ifdef stuff to avoid getting a warning about valid_idx not being
                // used when ENABLE_ASSERTS isn't defined.
    #ifdef ENABLE_ASSERTS
                const bool valid_idx = tuple_helpers::for_each<tuple,const F>::go(*this,funct,idx);
    #else
                tuple_helpers::for_each<tuple,const F>::go(*this,funct,idx);
    #endif
                DLIB_ASSERT(valid_idx,
                    "\tvoid tuple::for_index()"
                    << "\n\tYou have attempted to call for_index() with an index out of the valid range"
                    << "\n\tidx:  " << idx
                    << "\n\tthis: " << this
                    );
            }

            template <typename F>
            void for_index (
                const F& funct,
                long idx
            ) const
            {
                // do this #ifdef stuff to avoid getting a warning about valid_idx not being
                // used when ENABLE_ASSERTS isn't defined.
    #ifdef ENABLE_ASSERTS
                const bool valid_idx = tuple_helpers::for_each<const tuple,const F>::go(*this,funct,idx);
    #else
                tuple_helpers::for_each<const tuple,const F>::go(*this,funct,idx);
    #endif
                DLIB_ASSERT(valid_idx,
                    "\tvoid tuple::for_index()"
                    << "\n\tYou have attempted to call for_index() with an index out of the valid range"
                    << "\n\tidx:  " << idx
                    << "\n\tthis: " << this
                    );
            }

            template <typename F>
            void for_each (
                F& funct
            ) { tuple_helpers::for_each<tuple,F>::go(*this,funct); }

            template <typename F>
            void for_each (
                F& funct
            ) const { tuple_helpers::for_each<const tuple,F>::go(*this,funct); }

            template <typename F>
            void for_each (
                const F& funct
            ) const { tuple_helpers::for_each<const tuple,const F>::go(*this,funct); }

            template <typename F>
            void for_each (
                const F& funct
            ) { tuple_helpers::for_each<tuple,const F>::go(*this,funct); }

            inline friend void serialize (
                tuple& item,
                std::ostream& out
            )
            {
                try
                {
                    item.for_each(tuple_helpers::tuple_serialize(out));
                }
                catch (serialization_error& e)
                {
                    throw serialization_error(e.info + "\n   while serializing an object of type dlib::tuple<>");
                }
            }

            inline friend void deserialize (
                tuple& item,
                std::istream& in
            )
            {
                try
                {
                    item.for_each(tuple_helpers::tuple_deserialize(in));
                }
                catch (serialization_error& e)
                {
                    throw serialization_error(e.info + "\n   while deserializing an object of type dlib::tuple<>");
                }
            }

            inline friend void swap (
                tuple& a,
                tuple& b
            )
            {
                tuple_helpers::tuple_swap<tuple>::go(a,b);
            }

            inline void swap(
                tuple& item
            ) { tuple_helpers::tuple_swap<tuple>::go(item,*this); }

        };

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

    }

    #endif // DLIB_TUPLe_H_