![]() |
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