Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext
callable_context<>

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

PrevUpHomeNext