#ifndef BOOST_PP_IS_ITERATING
    ///////////////////////////////////////////////////////////////////////////////
    /// \file poly_function.hpp
    /// A wrapper that makes a tr1-style function object that handles const
    /// and non-const refs and reference_wrapper arguments, too, and forwards
    /// the arguments on to the specified implementation.
    //
    //  Copyright 2008 Eric Niebler. Distributed under the Boost
    //  Software License, Version 1.0. (See accompanying file
    //  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

    #ifndef BOOST_PROTO_DETAIL_POLY_FUNCTION_EAN_2008_05_02
    #define BOOST_PROTO_DETAIL_POLY_FUNCTION_EAN_2008_05_02

    #include <boost/ref.hpp>
    #include <boost/mpl/bool.hpp>
    #include <boost/mpl/void.hpp>
    #include <boost/mpl/eval_if.hpp>
    #include <boost/preprocessor/cat.hpp>
    #include <boost/preprocessor/facilities/intercept.hpp>
    #include <boost/preprocessor/iteration/iterate.hpp>
    #include <boost/preprocessor/repetition/enum.hpp>
    #include <boost/preprocessor/repetition/enum_params.hpp>
    #include <boost/preprocessor/repetition/enum_trailing_params.hpp>
    #include <boost/preprocessor/repetition/enum_binary_params.hpp>
    #include <boost/proto/proto_fwd.hpp>

    #ifdef _MSC_VER
    # pragma warning(push)
    # pragma warning(disable: 4181) // const applied to reference type
    #endif

    namespace boost { namespace proto { namespace detail
    {

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename T>
        struct normalize_arg
        {
            typedef T type;
            typedef T const &reference;
        };

        template<typename T>
        struct normalize_arg<T &>
        {
            typedef T type;
            typedef T const &reference;
        };

        template<typename T>
        struct normalize_arg<T const &>
        {
            typedef T type;
            typedef T const &reference;
        };

        template<typename T>
        struct normalize_arg<boost::reference_wrapper<T> >
        {
            typedef T &type;
            typedef T &reference;
        };

        template<typename T>
        struct normalize_arg<boost::reference_wrapper<T> &>
        {
            typedef T &type;
            typedef T &reference;
        };

        template<typename T>
        struct normalize_arg<boost::reference_wrapper<T> const &>
        {
            typedef T &type;
            typedef T &reference;
        };

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename T>
        struct arg
        {
            typedef T const &type;

            arg(type t)
              : value(t)
            {}

            operator type() const
            {
                return this->value;
            }

            type operator()() const
            {
                return *this;
            }

        private:
            arg &operator =(arg const &);
            type value;
        };

        template<typename T>
        struct arg<T &>
        {
            typedef T &type;

            arg(type t)
              : value(t)
            {}

            operator type() const
            {
                return this->value;
            }

            type operator()() const
            {
                return *this;
            }

        private:
            arg &operator =(arg const &);
            type value;
        };

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename T, typename Void = void>
        struct is_poly_function
          : mpl::false_
        {};

        template<typename T>
        struct is_poly_function<T, typename T::is_poly_function_base_>
          : mpl::true_
        {};

        ////////////////////////////////////////////////////////////////////////////////////////////////
        #define BOOST_PROTO_POLY_FUNCTION()                                                         \
            typedef void is_poly_function_base_;                                                    \
            /**/

        ////////////////////////////////////////////////////////////////////////////////////////////////
        struct poly_function_base
        {
            /// INTERNAL ONLY
            BOOST_PROTO_POLY_FUNCTION()
        };

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename Derived, typename NullaryResult = void>
        struct poly_function
          : poly_function_base
        {
            template<typename Sig>
            struct result;

            template<typename This>
            struct result<This()>
              : Derived::template impl<>
            {
                typedef typename result::result_type type;
            };

            NullaryResult operator()() const
            {
                result<Derived const()> impl;
                return impl();
            }

            #define BOOST_PP_ITERATION_PARAMS_1 (4, (1, BOOST_PROTO_MAX_ARITY, <boost/proto/detail/poly_function.hpp>, 0))
            #include BOOST_PP_ITERATE()
        };

        template<typename T>
        struct wrap_t;

        typedef char poly_function_t;
        typedef char (&mono_function_t)[2];
        typedef char (&unknown_function_t)[3];

        template<typename T> poly_function_t test_poly_function(T *, wrap_t<typename T::is_poly_function_base_> * = 0);
        template<typename T> mono_function_t test_poly_function(T *, wrap_t<typename T::result_type> * = 0);
        template<typename T> unknown_function_t test_poly_function(T *, ...);

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename Fun, typename Sig, std::size_t Switch = sizeof(test_poly_function<Fun>(0,0))>
        struct poly_function_traits
        {
            typedef typename Fun::template result<Sig>::type result_type;
            typedef Fun function_type;
        };

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename Fun, typename Sig>
        struct poly_function_traits<Fun, Sig, sizeof(mono_function_t)>
        {
            typedef typename Fun::result_type result_type;
            typedef Fun function_type;
        };

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename PolyFunSig, bool IsPolyFunction>
        struct as_mono_function_impl;

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename PolyFunSig>
        struct as_mono_function;

        #define BOOST_PP_ITERATION_PARAMS_1 (4, (1, BOOST_PROTO_MAX_ARITY, <boost/proto/detail/poly_function.hpp>, 1))
        #include BOOST_PP_ITERATE()

    }}} // namespace boost::proto::detail

    #ifdef _MSC_VER
    # pragma warning(pop)
    #endif

    #endif

#elif 0 == BOOST_PP_ITERATION_FLAGS()

    #define N BOOST_PP_ITERATION()

            template<typename This BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)>
            struct result<This(BOOST_PP_ENUM_PARAMS(N, A))>
              : Derived::template impl<
                    BOOST_PP_ENUM_BINARY_PARAMS(
                        N
                      , typename normalize_arg<A
                      , >::type BOOST_PP_INTERCEPT
                    )
                >
            {
                typedef typename result::result_type type;
            };

            template<BOOST_PP_ENUM_PARAMS(N, typename A)>
            typename result<
                Derived const(
                    BOOST_PP_ENUM_BINARY_PARAMS(N, A, const & BOOST_PP_INTERCEPT)
                )
            >::type
            operator ()(BOOST_PP_ENUM_BINARY_PARAMS(N, A, const &a)) const
            {
                result<
                    Derived const(
                        BOOST_PP_ENUM_BINARY_PARAMS(N, A, const & BOOST_PP_INTERCEPT)
                    )
                > impl;

                #define M0(Z, N, DATA)                                                              \
                    static_cast<typename normalize_arg<BOOST_PP_CAT(A, N) const &>                  \
                        ::reference>(BOOST_PP_CAT(a, N))
                return impl(BOOST_PP_ENUM(N, M0, ~));
                #undef M0
            }

    #undef N

#elif 1 == BOOST_PP_ITERATION_FLAGS()

    #define N BOOST_PP_ITERATION()

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename PolyFun BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)>
        struct poly_function_traits<PolyFun, PolyFun(BOOST_PP_ENUM_PARAMS(N, A)), sizeof(poly_function_t)>
        {
            typedef typename PolyFun::template impl<BOOST_PP_ENUM_PARAMS(N, const A)> function_type;
            typedef typename function_type::result_type result_type;
        };

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename PolyFun BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)>
        struct as_mono_function_impl<PolyFun(BOOST_PP_ENUM_PARAMS(N, A)), true>
        {
            typedef typename PolyFun::template impl<BOOST_PP_ENUM_PARAMS(N, const A)> type;
        };

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename PolyFun BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)>
        struct as_mono_function_impl<PolyFun(BOOST_PP_ENUM_PARAMS(N, A)), false>
        {
            typedef PolyFun type;
        };

        ////////////////////////////////////////////////////////////////////////////////////////////////
        template<typename PolyFun BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)>
        struct as_mono_function<PolyFun(BOOST_PP_ENUM_PARAMS(N, A))>
          : as_mono_function_impl<PolyFun(BOOST_PP_ENUM_PARAMS(N, A)), is_poly_function<PolyFun>::value>
        {};

    #undef N

#endif
