Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext
Controlling How Child Expressions Are Captured
[Note] 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.

Primer: 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 t is not a Proto expr...

When t is a Proto expr...

proto::as_expr(t)

Return (by value) a new Proto terminal holding t by value.

Return t by value unmodified.

proto::as_child(t)

Return (by value) a new Proto terminal holding t by reference.

Return t by reference unmodified.


[Note] Note

There is one important place where Proto uses both as_expr and as_child: proto::make_expr(). The proto::make_expr() function requires you to specify for each child whether it should be held by value or by reference. Proto uses proto::as_expr() to pre-process the children to be held by value, and proto::as_child() for the ones to be held by reference.

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.

Per-Domain 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
    {
        typedef unspecified-Proto-expr-type result_type;

        result_type operator()( T & t ) const
        {
            return unspecified-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.

Per-Domain 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
    {
        typedef unspecified-Proto-expr-type result_type;

        result_type operator()( T & t ) const
        {
            return unspecified-Proto-expr-object;
        }
    };
};
Making Proto Expressions 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.


PrevUpHomeNext