![]() |
Home | Libraries | People | FAQ | More |
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.
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.
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:
expr<>
terminal objects. These new wrappers
are not themselves held by reference, but the object wrapped is.
Notice that the type of the Protofied 42
literal is int const
&
-- held by reference.
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 |
---|---|
An astute reader will notice that the object |