![]() |
Home | Libraries | People | FAQ | More |
The proto::callable_context<> is a helper that simplifies the job of writing context classes. Rather than writing template specializations, with proto::callable_context<> you write a function object with an overloaded function call operator. Any expressions not handled by an overload are automatically dispatched to a default evaluation context that you can specify.
Rather than an evaluation context in its own right, proto::callable_context<> is more properly thought of as a context adaptor. To use it, you must define your own context that inherits from proto::callable_context<>.
In the null_context section, we saw how to implement an evaluation context that increments all the integers within an expression tree. Here is how to do the same thing with the proto::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:
i = 1 j = 11
In the increment_ints context, we didn't have to define any nested eval<> templates. That's because proto::callable_context<> implements them for us. proto::callable_context<> takes two template parameters: the derived context and a fall-back context. For each node in the expression tree being evaluated, proto::callable_context<> checks to see if there is an overloaded operator() in the derived 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::child_c<0>(expr) , proto::child_c<1>(expr) ... );
Using function overloading and metaprogramming tricks, proto::callable_context<> can detect at compile-time whether such a function exists or not. If so, that function is called. If not, the current expression is passed to the fall-back evaluation context to be processed.
We saw another example of the proto::callable_context<> when we looked at the simple calculator expression evaluator. There, we wanted to customize the evaluation of placeholder terminals, and delegate the handling of all other nodes to the proto::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 > { std::vector<double> args; // Define the result type of the calculator. typedef double result_type; // Handle the placeholders: template<int I> double operator()(proto::tag::terminal, placeholder<I>) const { return this->args[I]; } };
In this case, we didn't specify a fall-back context. In that case, proto::callable_context<> uses the proto::default_context. With the above calculator_context and a couple of appropriately defined placeholder terminals, we can evaluate calculator expressions, as demonstrated below:
template<int I> struct placeholder {}; terminal<placeholder<0> >::type const _1 = {{}}; terminal<placeholder<1> >::type const _2 = {{}}; // ... calculator_context ctx; ctx.args.push_back(4); ctx.args.push_back(5); double j = proto::eval( (_2 - _1) / _2 * 100, ctx ); std::cout << "j = " << j << std::endl;
The above code displays the following:
j = 20