Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext
Unpacking Expressions

Processing expressions with an arbitrary number of children can be a pain. What if you want to do something to each child, then pass the results as arguments to some other function? Can you do it just once without worrying about how many children an expression has? Yes. This is where Proto's unpacking expressions come in handy. Unpacking expressions give you a way to write callable and object transforms that handle n-ary expressions.

[Note] Note

Inspired by C++11 Variadic Templates

Proto's unpacking expressions take inspiration from the C++11 feature of the same name. If you are familiar with variadic functions, and in particular how to expand a function parameter pack, this discussion should seem very familiar. However, this feature doesn't actually use any C++11 features, so the code describe here will work with any compliant C++98 compiler.

Example: A C++ Expression Evaluator

Proto has the built-in proto::_default<> transform for evaluating Proto expressions in a C++-ish way. But if it didn't, it wouldn't be too hard to implement one from scratch using Proto's unpacking patterns. The transform eval below does just that.

// A callable polymorphic function object that takes an unpacked expression
// and a tag, and evaluates the expression. A plus tag and two operands adds
// them with operator +, for instance.
struct do_eval : proto::callable
{
    typedef double result_type;

#define UNARY_OP(TAG, OP)                                                       \
    template<typename Arg>                                                      \
    double operator()(proto::tag::TAG, Arg arg) const                           \
    {                                                                           \
        return OP arg;                                                          \
    }                                                                           \
    /**/

#define BINARY_OP(TAG, OP)                                                      \
    template<typename Left, typename Right>                                     \
    double operator()(proto::tag::TAG, Left left, Right right) const            \
    {                                                                           \
        return left OP right;                                                   \
    }                                                                           \
    /**/

    UNARY_OP(negate, -)
    BINARY_OP(plus, +)
    BINARY_OP(minus, -)
    BINARY_OP(multiplies, *)
    BINARY_OP(divides, /)
    /*... others ...*/
};

struct eval
  : proto::or_<
        // Evaluate terminals by simply returning their value
        proto::when<proto::terminal<_>, proto::_value>

        // Non-terminals are handled by unpacking the expression,
        // recursively calling eval on each child, and passing
        // the results along with the expression's tag to do_eval
        // defined above.
      , proto::otherwise<do_eval(proto::tag_of<_>(), eval(proto::pack(_))...)>
        // UNPACKING PATTERN HERE -------------------^^^^^^^^^^^^^^^^^^^^^^^^
    >
{};

The bulk of the above code is devoted to the do_eval function object that maps tag types to behaviors, but the interesting bit is the definition of the eval algorithm at the bottom. Terminals are handled quite simply, but non-terminals could be unary, binary, ternary, even n-ary if we consider function call expressions. The eval algorithm handles this uniformly with the help of an unpacking pattern.

Non-terminals are evaluated with this callable transform:

do_eval(proto::tag_of<_>(), eval(proto::pack(_))...)

You can read this as: call the do_eval function object with the tag of the current expression and all its children after they have each been evaluated with eval. The unpacking pattern is the bit just before the ellipsis: eval(proto::pack(_)).

What's going on here is this. The unpacking expression gets repeated once for each child in the expression currently being evaluated. In each repetition, the type proto::pack(_) gets replaced with proto::_child_c<N>. So, if a unary expression is passed to eval, it actually gets evaluated like this:

// After the unpacking pattern is expanded for a unary expression
do_eval(proto::tag_of<_>(), eval(proto::_child_c<0>))

And when passed a binary expression, the unpacking pattern expands like this:

// After the unpacking pattern is expanded for a binary expression
do_eval(proto::tag_of<_>(), eval(proto::_child_c<0>), eval(proto::_child_c<1>))

Although it can't happen in our example, when passed a terminal, the unpacking pattern expands such that it extracts the value from the terminal instead of the children. So it gets handled like this:

// If a terminal were passed to this transform, Proto would try
// to evaluate it like this, which would fail:
do_eval(proto::tag_of<_>(), eval(proto::_value))

That doesn't make sense. proto::_value would return something that isn't a Proto expression, and eval wouldn't be able to evaluate it. Proto algorithms don't work unless you pass them Proto expressions.

[Note] Note

Kickin' It Old School

You may be thinking, my compiler doesn't support C++11 variadic templates! How can this possibly work? The answer is simple: The ... above isn't a C++11 pack expansion. It's actually an old-school C-style vararg. Remember that callable and object transforms are function types. A transform with one of these pseudo-pack expansions is really just the type of a boring, old vararg function. Proto just interprets it differently.

Unpacking patterns are very expressive. Any callable or object transform can be used as an unpacking pattern, so long as proto::pack(_) appears exactly once somewhere within it. This gives you a lot of flexibility in how you want to process the children of an expression before passing them on to some function object or object constructor.


PrevUpHomeNext