evaluation.qbk

来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 560 行 · 第 1/2 页

QBK
560
字号
    #include <boost/xpressive/proto/proto.hpp>    #include <boost/xpressive/proto/context.hpp>    #include <boost/typeof/std/ostream.hpp>    using namespace boost;    proto::terminal< std::ostream & >::type cout_ = { std::cout };    template< typename Expr >    void evaluate( Expr const & expr )    {        // Evaluate the expression with default_context,        // to give the operators their C++ meanings:        proto::default_context ctx;        proto::eval(expr, ctx);    }    int main()    {        evaluate( cout_ << "hello" << ',' << " world" );        return 0;    }This program outputs the following:[prehello, world]_default_context_ is trivially defined in terms of a `default_eval<>`template, as follows:    // Definition of default_context    struct default_context    {        template<typename Expr>        struct eval          : default_eval<Expr, default_context const, typename Expr::proto_tag>        {};    };There are a bunch of `default_eval<>` specializations, each of which handlesa different C++ operator. Here, for instance, is the specialization for binaryaddition:    // A default expression evaluator for binary addition    template<typename Expr, typename Context>    struct default_eval<Expr, Context, proto::tag::plus>    {    private:        static Expr    & s_expr;        static Context & s_ctx;    public:        typedef            decltype(                proto::eval(proto::arg_c<0>(s_expr), s_ctx)              + proto::eval(proto::arg_c<1>(s_expr), s_ctx)            )        result_type;        result_type operator ()(Expr &expr, Context &ctx) const        {            return proto::eval(proto::arg_c<0>(expr), ctx)                 + proto::eval(proto::arg_c<1>(expr), ctx);        }    };The above code uses `decltype` to calculate the return type of the functioncall operator. `decltype` is a new keyword in the next version of C++ that getsthe type of any expression. Most compilers do not yet support `decltype`directly, so `default_eval<>` uses the Boost.Typeof library to emulate it. Onsome compilers, that may mean that `default_context` either doesn't work orthat it requires you to register your types with the Boost.Typeof library.Check the documentation for Boost.Typeof to see.[endsect][/===================================][section:null_context [^null_context]][/===================================]The _null_context_ is a simple context that recursively evaluates childrenbut does not combine the results in any way and returns void. It is usefulin conjunction with `callable_context<>`, or when defining your own contextswhich mutate an expression tree in-place rather than accumulate a result, aswe'll see below._null_context_ is trivially implemented in terms of `null_eval<>` as follows:    // Definition of null_context    struct null_context    {        template<typename Expr>        struct eval          : null_eval<Expr, null_context const, Expr::proto_arity::value>        {};    };And `null_eval<>` is also trivially implemented. Here, for instance isa binary `null_eval<>`:    // Binary null_eval<>    template<typename Expr, typename Context>    struct null_eval<Expr, Context, 2>    {        typedef void result_type;                void operator()(Expr &expr, Context &ctx) const        {            proto::eval(proto::arg_c<0>(expr), ctx);            proto::eval(proto::arg_c<1>(expr), ctx);        }    };When would such classes be useful? Imagine you have an expression tree withinteger terminals, and you would like to increment each integer in-place. Youmight define an evaluation context as follows:    struct increment_ints    {        // By default, just evaluate all children by defering        // to the null_eval<>        template<typename Expr, typename Arg = proto::result_of::arg<Expr>::type>        struct eval          : null_eval<Expr, increment_ints const>        {};                // Increment integer terminals        template<typename Expr>        struct eval<Expr, int>        {            typedef void result_type;                        void operator()(Expr &expr, increment_ints const &) const            {                ++proto::arg(expr);            }        };    };In the next section on _callable_context_, we'll see an even simpler way toachieve the same thing.[endsect][/=============================================][section:callable_context [^callable_context<>]][/=============================================]The _callable_context_ is a helper that simplifies the job of writing contextclasses. Rather than writing template specializations, with _callable_context_you write a function object with an overloaded function call operator. Anyexpressions not handled by an overload are automatically dispatched to adefault evaluation context that you can specify.Rather than an evaluation context in its own right, _callable_context_ is moreproperly thought of as a context adaptor. To use it, you must define your owncontext that inherits from _callable_context_.In the [link boost_proto.users_guide.expression_evaluation.canned_contexts.null_context [^null_context]]section, we saw how to implement an evaluation context that increments all theintegers within an expression tree. Here is how to do the same thing with the_callable_context_:    // An evaluation context that increments all    // integer terminals in-place.    struct increment_ints      : callable_context<            increment_ints const // derived context          , null_context const  // fall-back context        >    {        typedef void result_type;        // Handle int terminals here:        void operator()(proto::tag::terminal, int &i) const        {            ++i;        }    };With such a context, we can do the following:    literal<int> i = 0, j = 10;    proto::eval( i - j * 3.14, increment_ints() );        std::cout << "i = " << i.get() << std::endl;    std::cout << "j = " << j.get() << std::endl;This program outputs the following, which shows that the integers `i` and `j`have been incremented by `1`:[prei = 1j = 11]In the `increment_ints` context, we didn't have to define any nested `eval<>`templates. That's because _callable_context_ implements them for us._callable_context_ takes two template parameters: the derived context and afall-back context. For each node in the expression tree being evaluated,_callable_context_ checks to see if there is an overloaded `operator()` in thederived context that accepts it. Given some expression `expr` of type `Expr`,and a context `ctx`, it attempts to call:    ctx(        typename Expr::proto_tag()      , proto::arg_c<0>(expr)      , proto::arg_c<1>(expr)        ...    );Using function overloading and metaprogramming tricks, _callable_context_ candetect at compile-time whether such a function exists or not. If so, thatfunction is called. If not, the current expression is passed to the fall-backevaluation context to be processed.We saw another example of the _callable_context_ when we looked at the simplecalculator expression evaluator. There, we wanted to customize the evaluationof placeholder terminals, and delegate the handling of all other nodes to the_default_context_. We did that as follows:    // An evaluation context for calculator expressions that    // explicitly handles placeholder terminals, but defers the    // processing of all other nodes to the default_context.    struct calculator_context      : proto::callable_context< calculator_context const >    {        calculator_context(double d1, double d2)          : d1_(d1), d2_(d2)        {}        // Define the result type of the calculator.        typedef double result_type;        // Handle the placeholders:        double operator()(proto::tag::terminal, placeholder1) const        {            return this->d1_;        }        double operator()(proto::tag::terminal, placeholder2) const        {            return this->d2_;        }    private:        double d1_, d2_;    };In this case, we didn't specify a fall-back context. In that case,_callable_context_ uses the _default_context_. With the above`calculator_context` and a couple of appropriately defined placeholderterminals, we can evaluate calculator expressions, as demonstratedbelow:    struct placeholder1 {};    struct placeholder2 {};    terminal<placeholder1>::type const _1 = {{}};    terminal<placeholder2>::type const _2 = {{}};    // ...    double j = proto::eval(        (_2 - _1) / _2 * 100      , calculator_context(4, 5)    );    std::cout << "j = " << j << std::endl;The above code displays the following:[prej = 20][endsect][endsect][endsect]

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?