| Home | Libraries | People | FAQ | More

###### `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