Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Intermediate Form: Understanding and Introspecting Expressions

Accessing Parts of an Expression
Deep-copying Expressions
Debugging Expressions
Operator Tags and Metafunctions
Expressions as Fusion Sequences
Expression Introspection: Defining a Grammar
Finding Patterns in Expressions
Fuzzy and Exact Matches of Terminals
if_<>, and_<>, and not_<>
Improving Compile Times With switch_<>
Matching Vararg Expressions
Defining EDSL Grammars

By now, you know a bit about how to build a front-end for your EDSL "compiler" -- you can define terminals and functions that generate expression templates. But we haven't said anything about the expression templates themselves. What do they look like? What can you do with them? In this section we'll see.

The expr<> Type

All Proto expressions are an instantiation of a template called proto::expr<> (or a wrapper around such an instantiation). When we define a terminal as below, we are really initializing an instance of the proto::expr<> template.

// Define a placeholder type
template<int I>
struct placeholder
{};

// Define the Protofied placeholder terminal
proto::terminal< placeholder<0> >::type const _1 = {{}};

The actual type of _1 looks like this:

proto::expr< proto::tag::terminal, proto::term< placeholder<0> >, 0 >

The proto::expr<> template is the most important type in Proto. Although you will rarely need to deal with it directly, it's always there behind the scenes holding your expression trees together. In fact, proto::expr<> is the expression tree -- branches, leaves and all.

The proto::expr<> template makes up the nodes in expression trees. The first template parameter is the node type; in this case, proto::tag::terminal. That means that _1 is a leaf-node in the expression tree. The second template parameter is a list of child types, or in the case of terminals, the terminal's value type. Terminals will always have only one type in the type list. The last parameter is the arity of the expression. Terminals have arity 0, unary expressions have arity 1, etc.

The proto::expr<> struct is defined as follows:

template< typename Tag, typename Args, long Arity = Args::arity >
struct expr;

template< typename Tag, typename Args >
struct expr< Tag, Args, 1 >
{
    typedef typename Args::child0 proto_child0;
    proto_child0 child0;
    // ...
};

The proto::expr<> struct does not define a constructor, or anything else that would prevent static initialization. All proto::expr<> objects are initialized using aggregate initialization, with curly braces. In our example, _1 is initialized with the initializer {{}}. The outer braces are the initializer for the proto::expr<> struct, and the inner braces are for the member _1.child0 which is of type placeholder<0>. Note that we use braces to initialize _1.child0 because placeholder<0> is also an aggregate.

Building Expression Trees

The _1 node is an instantiation of proto::expr<>, and expressions containing _1 are also instantiations of proto::expr<>. To use Proto effectively, you won't have to bother yourself with the actual types that Proto generates. These are details, but you're likely to encounter these types in compiler error messages, so it's helpful to be familiar with them. The types look like this:

// The type of the expression -_1
typedef
    proto::expr<
        proto::tag::negate
      , proto::list1<
            proto::expr<
                proto::tag::terminal
              , proto::term< placeholder<0> >
              , 0
            > const &
        >
      , 1
    >
negate_placeholder_type;

negate_placeholder_type x = -_1;

// The type of the expression _1 + 42
typedef
    proto::expr<
        proto::tag::plus
      , proto::list2<
            proto::expr<
                proto::tag::terminal
              , proto::term< placeholder<0> >
              , 0
            > const &
          , proto::expr<
                proto::tag::terminal
              , proto::term< int const & >
              , 0
            >
        >
      , 2
    >
placeholder_plus_int_type;

placeholder_plus_int_type y = _1 + 42;

There are a few things to note about these types:

The types make it clear: everything in a Proto expression tree is held by reference. That means that building an expression tree is exceptionally cheap. It involves no copying at all.

[Note] Note

An astute reader will notice that the object y defined above will be left holding a dangling reference to a temporary int. In the sorts of high-performance applications Proto addresses, it is typical to build and evaluate an expression tree before any temporary objects go out of scope, so this dangling reference situation often doesn't arise, but it is certainly something to be aware of. Proto provides utilities for deep-copying expression trees so they can be passed around as value types without concern for dangling references.


PrevUpHomeNext