Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext
Defining an Evaluation Context

As we saw in the previous section, there is really not much to the proto::eval() function. Rather, all the interesting expression evaluation goes on within a context class. This section shows how to implement one from scratch.

All context classes have roughly the following form:

// A prototypical user-defined context.
struct MyContext
{
    // A nested eval<> class template
    template<
        typename Expr
      , typename Tag = typename proto::tag_of<Expr>::type
    >
    struct eval;

    // Handle terminal nodes here...
    template<typename Expr>
    struct eval<Expr, proto::tag::terminal>
    {
        // Must have a nested result_type typedef.
        typedef ... result_type;

        // Must have a function call operator that takes
        // an expression and the context.
        result_type operator()(Expr &expr, MyContext &ctx) const
        {
            return ...;
        }
    };

    // ... other specializations of struct eval<> ...
};

Context classes are nothing more than a collection of specializations of a nested eval<> class template. Each specialization handles a different expression type.

In the Hello Calculator section, we saw an example of a user-defined context class for evaluating calculator expressions. That context class was implemented with the help of Proto's proto::callable_context<>. If we were to implement it from scratch, it would look something like this:

// The calculator_context from the "Hello Calculator" section,
// implemented from scratch.
struct calculator_context
{
    // The values with which we'll replace the placeholders
    std::vector<double> args;

    template<
        typename Expr
        // defaulted template parameters, so we can
        // specialize on the expressions that need
        // special handling.
      , typename Tag = typename proto::tag_of<Expr>::type
      , typename Arg0 = typename proto::child_c<Expr, 0>::type
    >
    struct eval;

    // Handle placeholder terminals here...
    template<typename Expr, int I>
    struct eval<Expr, proto::tag::terminal, placeholder<I> >
    {
        typedef double result_type;

        result_type operator()(Expr &, MyContext &ctx) const
        {
            return ctx.args[I];
        }
    };

    // Handle other terminals here...
    template<typename Expr, typename Arg0>
    struct eval<Expr, proto::tag::terminal, Arg0>
    {
        typedef double result_type;

        result_type operator()(Expr &expr, MyContext &) const
        {
            return proto::child(expr);
        }
    };

    // Handle addition here...
    template<typename Expr, typename Arg0>
    struct eval<Expr, proto::tag::plus, Arg0>
    {
        typedef double result_type;

        result_type operator()(Expr &expr, MyContext &ctx) const
        {
            return proto::eval(proto::left(expr), ctx)
                 + proto::eval(proto::right(expr), ctx);
        }
    };

    // ... other eval<> specializations for other node types ...
};

Now we can use proto::eval() with the context class above to evaluate calculator expressions as follows:

// Evaluate an expression with a calculator_context
calculator_context ctx;
ctx.args.push_back(5);
ctx.args.push_back(6);
double d = proto::eval(_1 + _2, ctx);
assert(11 == d);

Defining a context from scratch this way is tedious and verbose, but it gives you complete control over how the expression is evaluated. The context class in the Hello Calculator example was much simpler. In the next section we'll see the helper class Proto provides to ease the job of implementing context classes.


PrevUpHomeNext