![]() |
Home | Libraries | People | FAQ | More |
![]() |
Note |
---|---|
This is an advanced topic. Feel free to skip this if you're just getting started with Proto. |
Proto's operator overloads build expressions from sub-expressions. The sub-expressions become children of the new expression. By default, the children are stored in the parent by reference. This section describes how to change that default.
as_child
vs. as_expr
Proto lets you independently customize the behavior of proto::as_child()
and proto::as_expr()
.
Both accept an object x
and return a Proto expression
by turning x
it into a Proto terminal if necessary.
Although similar, the two functions are used in different situations
and have subtly different behavior by default. It's important to understand
the difference so that you know which to customize to achieve the behavior
you want.
To wit: proto::as_expr()
is typically used by
you to turn an object into a Proto expression that
is to be held in a local variable, as so:
auto l = proto::as_expr(x); // Turn x into a Proto expression, hold the result in a local
The above works regardless of whether x
is already a Proto expression or not. The object l
is guaranteed to be a valid Proto expression. If x
is a non-Proto object, it is turned into a terminal expression that holds
x
by value.[2] If x
is a
Proto object already, proto::as_expr()
returns it by value unmodified.
In contrast, proto::as_child()
is used internally by Proto to pre-process objects before making them
children of another expression. Since it's internal to Proto, you don't
see it explicitly, but it's there behind the scenes in expressions like
this:
x + y; // Consider that y is a Proto expression, but x may or may not be.
In this case, Proto builds a plus node from the two children. Both are
pre-processed by passing them to proto::as_child()
before making them children of the new node. If x
is not a Proto expression, it becomes one by being wrapped in a Proto
terminal that holds it by reference. If x
is already a Proto expression, proto::as_child()
returns it by
reference unmodified. Contrast this with the above description
for proto::as_expr()
.
The table below summarizes the above description.
Table 1.3. proto::as_expr() vs. proto::as_child()
Function |
When |
When |
---|---|---|
|
Return (by value) a new Proto terminal holding |
Return |
|
Return (by value) a new Proto terminal holding |
Return |
![]() |
Note |
---|---|
There is one important place where Proto uses both |
Now that you know what proto::as_child()
and proto::as_expr()
are, where they are
used, and what they do by default, you may decide that one or both of
these functions should have different behavior for your domain. For instance,
given the above description of proto::as_child()
,
the following code is always wrong:
proto::literal<int> i(0); auto l = i + 42; // This is WRONG! Don't do this.
Why is this wrong? Because proto::as_child()
will turn the integer literal 42 into a Proto terminal that holds a reference
to a temporary integer initialized with 42. The lifetime of that temporary
ends at the semicolon, guaranteeing that the local l
is left holding a dangling reference to a deceased integer. What to do?
One answer is to use proto::deep_copy()
.
Another is to customize the behavior of proto::as_child()
for your domain. Read on for the details.
as_child
To control how Proto builds expressions out of sub-expressions in your
domain, define your domain as usual, and then define a nested as_child<>
class template within it, as follows:
class my_domain : proto::domain< my_generator, my_grammar > { // Here is where you define how Proto should handle // sub-expressions that are about to be glommed into // a larger expression. template< typename T > struct as_child { typedefunspecified-Proto-expr-type
result_type; result_type operator()( T & t ) const { returnunspecified-Proto-expr-object
; } }; };
There's one important thing to note: in the above code, the template
parameter T
may or may not be a Proto expression type,
but the result must be a Proto expression type,
or a reference to one. That means that most user-defined as_child<>
templates will need to check whether T
is an expression
or not (using proto::is_expr<>
), and then turn non-expressions
into Proto terminals by wrapping them as proto::terminal< /* ... */
>::type
or equivalent.
as_expr
Although less common, Proto also lets you customize the behavior of
proto::as_expr()
on a per-domain basis.
The technique is identical to that for as_child
. See
below:
class my_domain : proto::domain< my_generator, my_grammar > { // Here is where you define how Proto should handle // objects that are to be turned into expressions // fit for storage in local variables. template< typename T > struct as_expr { typedefunspecified-Proto-expr-type
result_type; result_type operator()( T & t ) const { returnunspecified-Proto-expr-object
; } }; };
auto
-safe
Let's look again at the problem described above involving the C++11
auto
keyword and the default
behavior of proto::as_child()
.
proto::literal<int> i(0); auto l = i + 42; // This is WRONG! Don't do this.
Recall that the problem is the lifetime of the temporary integer created
to hold the value 42. The local l
will be left holding a dangling reference to it after its lifetime is
over. What if we want Proto to make expressions safe to store this way
in local variables? We can do so very easily by making proto::as_child()
behave just like proto::as_expr()
. The following code
achieves this:
template< typename E > struct my_expr; struct my_generator : proto::pod_generator< my_expr > {}; struct my_domain : proto::domain< my_generator > { // Make as_child() behave like as_expr() in my_domain. // (proto_base_domain is a typedef for proto::domain< my_generator > // that is defined in proto::domain<>.) template< typename T > struct as_child : proto_base_domain::as_expr< T > {}; }; template< typename E > struct my_expr { BOOST_PROTO_EXTENDS( E, my_expr< E >, my_domain ) }; /* ... */ proto::literal< int, my_domain > i(0); auto l = i + 42; // OK! Everything is stored by value here.
Notice that my_domain::as_child<>
simply defers to the default
implementation of as_expr<>
found in proto::domain<>
.
By simply cross-wiring our domain's as_child<>
to as_expr<>
, we guarantee that all terminals
that can be held by value are, and that all child expressions are also
held by value. This increases copying and may incur a runtime performance
cost, but it eliminates any spector of lifetime management issues.
For another example, see the definition of lldomain
in libs/proto/example/lambda.hpp
. That example is
a complete reimplementation of the Boost Lambda Library (BLL) on top
of Boost.Proto. The function objects the BLL generates are safe to be
stored in local variables. To emulate this with Proto, the lldomain
cross-wires as_child<>
to as_expr<>
as above, but with one extra twist: objects with array type are also
stored by reference. Check it out.
[2]
It's not always possible to hold something by value. By default, proto::as_expr()
makes an exception
for functions, abstract types, and iostreams (types derived from std::ios_base
). These objects are held
by reference. All others are held by value, even arrays.