There are certain idioms that Boost.YAP is written to support. Before getting into the nuts and bolts of how Boost.YAP operates, let's define these idioms.
evaluate(transform())
This is the main idiom you'll see reinforced in the examples. The idea is that you capture an expression:
auto expr_0 = /* ... */ ;
then transform it one or more times:
auto expr_1 = boost::yap::transform(expr_0, my_transform_1); auto expr_2 = boost::yap::transform(expr_1, my_transform_2); // ... auto expr_n = boost::yap::transform(expr_n_minus_1, my_transform_n);
and then finally you evaluate it:
auto const result = boost::yap::evaluate(expr_n);
Each call to transform()
here produces a new Expression that can subsequently
be transformed. This is conceptually similar to what happens inside many
compilers. Capturing the expression is analogous to the compiler's parse;
the transformations are analogous to optimization passes; and the evaluation
is analogous to code generation.
This keeps the meaning of your code quite clear and easy to follow. For this reason, I think you should try to use Boost.YAP in this way when you can.
transform()
-as-evaluate
This is a variant of evaluate(transform())
, where the evaluate()
call at the end is unnecessary, because the final (or perhaps only) transform
does all the evaluation we need.
For instance, here is the get_arity
transform object used in the Calc3
example (don't worry too much about the implementation — we'll return
to this later in the docs in much greater detail):
struct get_arity { // Base case 1: Match a placeholder terminal, and return its arity as the // result. template <long long I> boost::hana::llong<I> operator() (boost::yap::expr_tag<boost::yap::expr_kind::terminal>, boost::yap::placeholder<I>) { return boost::hana::llong_c<I>; } // Base case 2: Match any other terminal. Return 0; non-placeholders do // not contribute to arity. template <typename T> auto operator() (boost::yap::expr_tag<boost::yap::expr_kind::terminal>, T &&) { using namespace boost::hana::literals; return 0_c; } // Recursive case: Match any expression not covered above, and return the // maximum of its children's arities. template <boost::yap::expr_kind Kind, typename... Arg> auto operator() (boost::yap::expr_tag<Kind>, Arg &&... arg) { return boost::hana::maximum( boost::hana::make_tuple( boost::yap::transform( boost::yap::as_expr(std::forward<Arg>(arg)), get_arity{} )... ) ); } };
Here is how this might be used:
auto expr = 1_p * 2_p; auto const arity = boost::yap::transform(expr, get_arity{}); static_assert(arity.value == 2, "Called with wrong number of args.");
In this case, transform()
produces a non-Expression
value, all by itself. We got our result without ever needing to call evaluate()
.
Note | |
---|---|
Whether |