![]() |
Home | Libraries | People | FAQ | More |
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.