Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Accessing Parts of an Expression

After assembling an expression into a tree, you'll naturally want to be able to do the reverse, and access a node's children. You may even want to be able to iterate over the children with algorithms from the Boost.Fusion library. This section shows how.

Getting Expression Tags and Arities

Every node in an expression tree has both a tag type that describes the node, and an arity corresponding to the number of child nodes it has. You can use the proto::tag_of<> and proto::arity_of<> metafunctions to fetch them. Consider the following:

template<typename Expr>
void check_plus_node(Expr const &)
{
    // Assert that the tag type is proto::tag::plus
    BOOST_STATIC_ASSERT((
        boost::is_same<
            typename proto::tag_of<Expr>::type
          , proto::tag::plus
        >::value
    ));

    // Assert that the arity is 2
    BOOST_STATIC_ASSERT( proto::arity_of<Expr>::value == 2 );
}

// Create a binary plus node and use check_plus_node()
// to verify its tag type and arity:
check_plus_node( proto::lit(1) + 2 );

For a given type Expr, you could access the tag and arity directly as Expr::proto_tag and Expr::proto_arity, where Expr::proto_arity is an MPL Integral Constant.

Getting Terminal Values

There is no simpler expression than a terminal, and no more basic operation than extracting its value. As we've already seen, that is what proto::value() is for.

proto::terminal< std::ostream & >::type cout_ = {std::cout};

// Get the value of the cout_ terminal:
std::ostream & sout = proto::value( cout_ );

// Assert that we got back what we put in:
assert( &sout == &std::cout );

To compute the return type of the proto::value() function, you can use proto::result_of::value<>. When the parameter to proto::result_of::value<> is a non-reference type, the result type of the metafunction is the type of the value as suitable for storage by value; that is, top-level reference and qualifiers are stripped from it. But when instantiated with a reference type, the result type has a reference added to it, yielding a type suitable for storage by reference. If you want to know the actual type of the terminal's value including whether it is stored by value or reference, you can use fusion::result_of::value_at<Expr, 0>::type.

The following table summarizes the above paragraph.

Table 1.4. Accessing Value Types

Metafunction Invocation

When the Value Type Is ...

The Result Is ...

proto::result_of::value<Expr>::type

T

typename boost::remove_const<
    typename boost::remove_reference<T>::type
>::type [a]

proto::result_of::value<Expr &>::type

T

typename boost::add_reference<T>::type

proto::result_of::value<Expr const &>::type

T

typename boost::add_reference<
    typename boost::add_const<T>::type
>::type

fusion::result_of::value_at<Expr, 0>::type

T

T

[a] If T is a reference-to-function type, then the result type is simply T.


Getting Child Expressions

Each non-terminal node in an expression tree corresponds to an operator in an expression, and the children correspond to the operands, or arguments of the operator. To access them, you can use the proto::child_c() function template, as demonstrated below:

proto::terminal<int>::type i = {42};

// Get the 0-th operand of an addition operation:
proto::terminal<int>::type &ri = proto::child_c<0>( i + 2 );

// Assert that we got back what we put in:
assert( &i == &ri );

You can use the proto::result_of::child_c<> metafunction to get the type of the Nth child of an expression node. Usually you don't care to know whether a child is stored by value or by reference, so when you ask for the type of the Nth child of an expression Expr (where Expr is not a reference type), you get the child's type after references and cv-qualifiers have been stripped from it.

template<typename Expr>
void test_result_of_child_c(Expr const &expr)
{
    typedef typename proto::result_of::child_c<Expr, 0>::type type;

    // Since Expr is not a reference type,
    // result_of::child_c<Expr, 0>::type is a
    // non-cv qualified, non-reference type:
    BOOST_MPL_ASSERT((
        boost::is_same< type, proto::terminal<int>::type >
    ));
}

// ...
proto::terminal<int>::type i = {42};
test_result_of_child_c( i + 2 );

However, if you ask for the type of the Nth child of Expr & or Expr const & (note the reference), the result type will be a reference, regardless of whether the child is actually stored by reference or not. If you need to know exactly how the child is stored in the node, whether by reference or by value, you can use fusion::result_of::value_at<Expr, N>::type. The following table summarizes the behavior of the proto::result_of::child_c<> metafunction.

Table 1.5. Accessing Child Types

Metafunction Invocation

When the Child Is ...

The Result Is ...

proto::result_of::child_c<Expr, N>::type

T

typename boost::remove_const<
    typename boost::remove_reference<T>::type
>::type

proto::result_of::child_c<Expr &, N>::type

T

typename boost::add_reference<T>::type

proto::result_of::child_c<Expr const &, N>::type

T

typename boost::add_reference<
    typename boost::add_const<T>::type
>::type

fusion::result_of::value_at<Expr, N>::type

T

T


Common Shortcuts

Most operators in C++ are unary or binary, so accessing the only operand, or the left and right operands, are very common operations. For this reason, Proto provides the proto::child(), proto::left(), and proto::right() functions. proto::child() and proto::left() are synonymous with proto::child_c<0>(), and proto::right() is synonymous with proto::child_c<1>().

There are also proto::result_of::child<>, proto::result_of::left<>, and proto::result_of::right<> metafunctions that merely forward to their proto::result_of::child_c<> counterparts.


PrevUpHomeNext