#ifndef BOOST_PP_IS_ITERATING
    ///////////////////////////////////////////////////////////////////////////////
    /// \file make.hpp
    /// Contains definition of the make<> transform.
    //
    //  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_TRANSFORM_MAKE_HPP_EAN_12_02_2007
    #define BOOST_PROTO_TRANSFORM_MAKE_HPP_EAN_12_02_2007

    #include <boost/proto/detail/prefix.hpp>
    #include <boost/detail/workaround.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/preprocessor/repetition/enum_params_with_a_default.hpp>
    #include <boost/preprocessor/repetition/repeat_from_to.hpp>
    #include <boost/preprocessor/facilities/intercept.hpp>
    #include <boost/preprocessor/cat.hpp>
    #include <boost/preprocessor/iteration/iterate.hpp>
    #include <boost/preprocessor/selection/max.hpp>
    #include <boost/preprocessor/arithmetic/inc.hpp>
    #include <boost/mpl/aux_/has_type.hpp>
    #include <boost/mpl/aux_/template_arity.hpp>
    #include <boost/mpl/aux_/lambda_arity_param.hpp>
    #include <boost/utility/result_of.hpp>
    #include <boost/type_traits/remove_const.hpp>
    #include <boost/type_traits/remove_reference.hpp>
    #include <boost/proto/proto_fwd.hpp>
    #include <boost/proto/traits.hpp>
    #include <boost/proto/args.hpp>
    #include <boost/proto/transform/impl.hpp>
    #include <boost/proto/detail/as_lvalue.hpp>
    #include <boost/proto/detail/ignore_unused.hpp>
    #include <boost/proto/detail/suffix.hpp>

    namespace boost { namespace proto
    {
        namespace detail
        {
            template<BOOST_PP_ENUM_PARAMS_WITH_A_DEFAULT(BOOST_PROTO_MAX_ARITY, typename A, void)>
            struct typelist
            {
                typedef void type;
            };

            template<typename T, bool HasType = mpl::aux::has_type<T>::value>
            struct nested_type
            {
                typedef typename T::type type;
            };

            template<typename T>
            struct nested_type<T, false>
            {
                typedef T type;
            };

            template<typename T, typename Args, typename Void = void>
            struct nested_type_if
              : nested_type<T>
            {};

            template<typename R, typename Expr, typename State, typename Data
                , bool IsTransform = is_callable<R>::value
            >
            struct make_if_;

            template<typename R, typename Expr, typename State, typename Data
                BOOST_MPL_AUX_LAMBDA_ARITY_PARAM(long Arity = mpl::aux::template_arity<R>::value)
            >
            struct make_
            {
                typedef R type;
                typedef void not_applied_;
            };

            template<typename R, typename Expr, typename State, typename Data>
            struct make_if_<R, Expr, State, Data, false>
              : make_<R, Expr, State, Data>
            {};

            #if BOOST_WORKAROUND(__GNUC__, == 3) || (__GNUC__ == 4 && __GNUC_MINOR__ == 0)
            // work around GCC bug
            template<typename Tag, typename Args, long N, typename Expr, typename State, typename Data>
            struct make_if_<proto::expr<Tag, Args, N>, Expr, State, Data, false>
            {
                typedef proto::expr<Tag, Args, N> type;
                typedef void not_applied_;
            };
            #endif

            // TODO could optimize this if R is a transform
            template<typename R, typename Expr, typename State, typename Data>
            struct make_if_<R, Expr, State, Data, true>
              : remove_const<typename remove_reference<
                    typename boost::result_of<R(Expr, State, Data)>::type
                >::type>
            {};

            template<typename Type, bool IsAggregate = is_aggregate<Type>::value>
            struct construct_
            {
                typedef Type result_type;

                Type operator ()() const
                {
                    return Type();
                }

                #define TMP(Z, N, DATA)                                                             \
                template<BOOST_PP_ENUM_PARAMS_Z(Z, N, typename A)>                                  \
                Type operator ()(BOOST_PP_ENUM_BINARY_PARAMS_Z(Z, N, A, &a)) const                  \
                {                                                                                   \
                    return Type(BOOST_PP_ENUM_PARAMS_Z(Z, N, a));                                   \
                }
                BOOST_PP_REPEAT_FROM_TO(1, BOOST_PP_INC(BOOST_PROTO_MAX_ARITY), TMP, ~)
                #undef TMP
            };

            template<typename Type>
            struct construct_<Type, true>
            {
                typedef Type result_type;

                Type operator ()() const
                {
                    return Type();
                }

                #define TMP(Z, N, DATA)                                                             \
                template<BOOST_PP_ENUM_PARAMS_Z(Z, N, typename A)>                                  \
                Type operator ()(BOOST_PP_ENUM_BINARY_PARAMS_Z(Z, N, A, &a)) const                  \
                {                                                                                   \
                    Type that = {BOOST_PP_ENUM_PARAMS_Z(Z, N, a)};                                  \
                    return that;                                                                    \
                }
                BOOST_PP_REPEAT_FROM_TO(1, BOOST_PP_INC(BOOST_PROTO_MAX_ARITY), TMP, ~)
                #undef TMP
            };

            #define TMP(Z, N, DATA)                                                                 \
            template<typename Type BOOST_PP_ENUM_TRAILING_PARAMS_Z(Z, N, typename A)>               \
            Type construct(BOOST_PP_ENUM_BINARY_PARAMS_Z(Z, N, A, &a))                              \
            {                                                                                       \
                return construct_<Type>()(BOOST_PP_ENUM_PARAMS_Z(Z, N, a));                         \
            }
            BOOST_PP_REPEAT(BOOST_PROTO_MAX_ARITY, TMP, ~)
            #undef TMP
        }

        /// \brief A PrimitiveTransform which prevents another PrimitiveTransform
        /// from being applied in an \c ObjectTransform.
        ///
        /// When building higher order transforms with <tt>make\<\></tt> or
        /// <tt>lazy\<\></tt>, you sometimes would like to build types that
        /// are parameterized with Proto transforms. In such lambda-style
        /// transforms, Proto will unhelpfully find all nested transforms
        /// and apply them, even if you don't want them to be applied. Consider
        /// the following transform, which will replace the \c _ in
        /// <tt>Bar<_>()</tt> with <tt>proto::terminal\<int\>::type</tt>:
        ///
        /// \code
        /// template<typename T>
        /// struct Bar
        /// {};
        /// 
        /// struct Foo
        ///   : proto::when<_, Bar<_>() >
        /// {};
        /// 
        /// proto::terminal<int>::type i = {0};
        /// 
        /// int main()
        /// {
        ///     Foo()(i);
        ///     std::cout << typeid(Foo()(i)).name() << std::endl;
        /// }
        /// \endcode
        ///
        /// If you actually wanted to default-construct an object of type
        /// <tt>Bar\<_\></tt>, you would have to protect the \c _ to prevent
        /// it from being applied. You can use <tt>proto::protect\<\></tt>
        /// as follows:
        ///
        /// \code
        /// // OK: replace anything with Bar<_>()
        /// struct Foo
        ///   : proto::when<_, Bar<protect<_> >() >
        /// {};
        /// \endcode
        template<typename PrimitiveTransform>
        struct protect : transform<protect<PrimitiveTransform> >
        {
            template<typename, typename, typename>
            struct impl
            {
                typedef PrimitiveTransform result_type;
            };
        };

        /// \brief A PrimitiveTransform which computes a type by evaluating any
        /// nested transforms and then constructs an object of that type.
        ///
        /// The <tt>make\<\></tt> transform checks to see if \c Object is a template.
        /// If it is, the template type is disassembled to find nested transforms.
        /// Proto considers the following types to represent transforms:
        ///
        /// \li Function types
        /// \li Function pointer types
        /// \li Types for which <tt>proto::is_callable\< type \>::::value</tt> is \c true
        ///
        /// <tt>make\<T\<X0,X1,...\> \>::::result\<void(Expr, State, Data)\>::::type</tt>
        /// is evaluated as follows. For each \c X in <tt>X0,X1,...</tt>, do:
        ///
        /// \li If \c X is a template like <tt>U\<Y0,Y1,...\></tt>, then let <tt>X'</tt>
        ///     be <tt>make\<U\<Y0,Y1,...\> \>::::result\<void(Expr, State, Data)\>::::type</tt>
        ///     (which evaluates this procedure recursively). Note whether any
        ///     substitutions took place during this operation.
        /// \li Otherwise, if \c X is a transform, then let <tt>X'</tt> be
        ///     <tt>when\<_, X\>::::result\<void(Expr, State, Data)\>::::type</tt>.
        ///     Note that a substitution took place.
        /// \li Otherwise, let <tt>X'</tt> be \c X, and note that no substitution
        ///     took place.
        /// \li If any substitutions took place in any of the above steps and
        ///     <tt>T\<X0',X1',...\></tt> has a nested <tt>::type</tt> typedef,
        ///     the result type is <tt>T\<X0',X1',...\>::::type</tt>.
        /// \li Otherwise, the result type is <tt>T\<X0',X1',...\></tt>.
        ///
        /// Note that <tt>when\<\></tt> is implemented in terms of <tt>call\<\></tt>
        /// and <tt>make\<\></tt>, so the above procedure is evaluated recursively.
        template<typename Object>
        struct make : transform<make<Object> >
        {
            template<typename Expr, typename State, typename Data>
            struct impl : transform_impl<Expr, State, Data>
            {
                typedef typename detail::make_if_<Object, Expr, State, Data>::type result_type;

                /// \return <tt>result_type()</tt>
                result_type operator ()(
                    typename impl::expr_param
                  , typename impl::state_param
                  , typename impl::data_param
                ) const
                {
                    return result_type();
                }
            };
        };

        #define BOOST_PP_ITERATION_PARAMS_1 (3, (0, BOOST_PROTO_MAX_ARITY, <boost/proto/transform/make.hpp>))
        #include BOOST_PP_ITERATE()

        /// INTERNAL ONLY
        ///
        template<typename Object>
        struct is_callable<make<Object> >
          : mpl::true_
        {};

        /// INTERNAL ONLY
        ///
        template<typename PrimitiveTransform>
        struct is_callable<protect<PrimitiveTransform> >
          : mpl::true_
        {};
    }}

    #endif

#else

    #define N BOOST_PP_ITERATION()

        namespace detail
        {
            #if N > 0
            template<typename T BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)>
            struct nested_type_if<
                T
              , typelist<BOOST_PP_ENUM_PARAMS(N, A)>
              , typename typelist<
                    BOOST_PP_ENUM_BINARY_PARAMS(N, typename A, ::not_applied_ BOOST_PP_INTERCEPT)
                >::type
            >
            {
                typedef T type;
                typedef void not_applied_;
            };

            #define TMP0(Z, M, DATA) make_if_<BOOST_PP_CAT(A, M), Expr, State, Data>
            #define TMP1(Z, M, DATA) typename TMP0(Z, M, DATA) ::type

            template<
                template<BOOST_PP_ENUM_PARAMS(N, typename BOOST_PP_INTERCEPT)> class R
                BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)
              , typename Expr, typename State, typename Data
            >
            struct make_<R<BOOST_PP_ENUM_PARAMS(N, A)>, Expr, State, Data
                BOOST_MPL_AUX_LAMBDA_ARITY_PARAM(N)
            >
              : nested_type_if<
                    R<BOOST_PP_ENUM(N, TMP1, ~)>
                  , typelist<BOOST_PP_ENUM(N, TMP0, ~) >
                >
            {};

            template<
                template<BOOST_PP_ENUM_PARAMS(N, typename BOOST_PP_INTERCEPT)> class R
                BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)
              , typename Expr, typename State, typename Data
            >
            struct make_<noinvoke<R<BOOST_PP_ENUM_PARAMS(N, A)> >, Expr, State, Data
                BOOST_MPL_AUX_LAMBDA_ARITY_PARAM(N)
            >
            {
                typedef R<BOOST_PP_ENUM(N, TMP1, ~)> type;
            };

            #undef TMP0
            #undef TMP1
            #endif

            template<
                typename R
                BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)
              , typename Expr, typename State, typename Data
            >
            struct make_if_<R(BOOST_PP_ENUM_PARAMS(N, A)), Expr, State, Data, false>
            {
                typedef
                    typename remove_const<
                        typename remove_reference<
                            typename when<_, R(BOOST_PP_ENUM_PARAMS(N, A))>
                                ::template impl<Expr, State, Data>::result_type
                        >::type
                    >::type
                type;
            };

            template<
                typename R
                BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)
              , typename Expr, typename State, typename Data
            >
            struct make_if_<R(*)(BOOST_PP_ENUM_PARAMS(N, A)), Expr, State, Data, false>
            {
                typedef
                    typename remove_const<
                        typename remove_reference<
                            typename when<_, R(BOOST_PP_ENUM_PARAMS(N, A))>
                                ::template impl<Expr, State, Data>::result_type
                        >::type
                    >::type
                type;
            };

            template<typename T, typename A>
            struct construct_<proto::expr<T, A, N>, true>
            {
                typedef proto::expr<T, A, N> result_type;

                template<BOOST_PP_ENUM_PARAMS(BOOST_PP_MAX(N, 1), typename A)>
                result_type operator ()(BOOST_PP_ENUM_BINARY_PARAMS(BOOST_PP_MAX(N, 1), A, &a)) const
                {
                    return result_type::make(BOOST_PP_ENUM_PARAMS(BOOST_PP_MAX(N, 1), a));
                }
            };
        }

        /// \brief A PrimitiveTransform which computes a type by evaluating any
        /// nested transforms and then constructs an object of that type with the
        /// current expression, state and data, transformed according
        /// to \c A0 through \c AN.
        template<typename Object BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)>
        struct make<Object(BOOST_PP_ENUM_PARAMS(N, A))>
          : transform<make<Object(BOOST_PP_ENUM_PARAMS(N, A))> >
        {
            template<typename Expr, typename State, typename Data>
            struct impl : transform_impl<Expr, State, Data>
            {
                /// \brief <tt>make\<Object\>::::result\<void(Expr, State, Data)\>::::type</tt>
                typedef typename detail::make_if_<Object, Expr, State, Data>::type result_type;
                //typedef typename detail::make_<Object, Expr, State, Data>::type result_type;

                /// Let \c ax be <tt>when\<_, Ax\>()(e, s, d)</tt>
                /// for each \c x in <tt>[0,N]</tt>.
                /// Let \c T be <tt>result\<void(Expr, State, Data)\>::::type</tt>.
                /// Return <tt>T(a0, a1,... aN)</tt>.
                ///
                /// \param e The current expression
                /// \param s The current state
                /// \param d An arbitrary data
                result_type operator ()(
                    typename impl::expr_param   e
                  , typename impl::state_param  s
                  , typename impl::data_param   d
                ) const
                {
                    proto::detail::ignore_unused(e);
                    proto::detail::ignore_unused(s);
                    proto::detail::ignore_unused(d);
                    return detail::construct<result_type>(
                        #define TMP(Z, M, DATA)                                                     \
                            detail::as_lvalue(                                                      \
                                typename when<_, BOOST_PP_CAT(A, M)>                                \
                                    ::template impl<Expr, State, Data>()(e, s, d)         \
                            )
                        BOOST_PP_ENUM(N, TMP, DATA)
                        #undef TMP
                    );
                }
            };
        };

        #if BOOST_WORKAROUND(__GNUC__, == 3) || (__GNUC__ == 4 && __GNUC_MINOR__ == 0)
        // work around GCC bug
        template<typename Tag, typename Args, long Arity BOOST_PP_ENUM_TRAILING_PARAMS(N, typename A)>
        struct make<proto::expr<Tag, Args, Arity>(BOOST_PP_ENUM_PARAMS(N, A))>
          : transform<make<proto::expr<Tag, Args, Arity>(BOOST_PP_ENUM_PARAMS(N, A))> >
        {
            template<typename Expr, typename State, typename Data>
            struct impl : transform_impl<Expr, State, Data>
            {
                typedef proto::expr<Tag, Args, Arity> result_type;

                result_type operator ()(
                    typename impl::expr_param   e
                  , typename impl::state_param  s
                  , typename impl::data_param   d
                ) const
                {
                    return proto::expr<Tag, Args, Arity>::make(
                        #define TMP(Z, M, DATA)                                                     \
                            detail::as_lvalue(                                                      \
                                typename when<_, BOOST_PP_CAT(A, M)>                                \
                                    ::template impl<Expr, State, Data>()(e, s, d)         \
                            )
                        BOOST_PP_ENUM(N, TMP, DATA)
                        #undef TMP
                    );
                }
            };
        };
        #endif

    #undef N

#endif
