mirror of
https://github.com/boostorg/yap.git
synced 2026-02-22 16:02:10 +00:00
407 lines
17 KiB
Plaintext
407 lines
17 KiB
Plaintext
[section Tutorial]
|
|
|
|
[section Expressions]
|
|
|
|
_yap_ consists of expressions and functions that operate on them. Any type
|
|
that models the _Expr_ concept will work with any of the functions in _yap_
|
|
that takes an expression.
|
|
|
|
For a type `T` to model the _Expr_ concept, `T` must contain at least an
|
|
_kind_ (terminal, plus-operation, etc.) and a _tuple_ of values. That's it.
|
|
This means that user-defined templates modelling _ExprTmpl_ are very
|
|
straightforward to make.
|
|
|
|
[note The _tuple_ of values is also constrained, based on the kind of the
|
|
expression; see the full _Expr_ documentation for details.]
|
|
|
|
Here's an example:
|
|
|
|
[minimal_template]
|
|
|
|
That's a template that models _ExprTmpl_. Instantiated with the proper
|
|
template parameters, it produces _Exprs_.
|
|
|
|
Ok, so it's not that interesting by itself -- it has no operators. But we can
|
|
still use it with the _yap_ functions that take an _Expr_. Let's make a
|
|
plus-expression manually:
|
|
|
|
[minimal_template_manual_construction]
|
|
|
|
And if we evaluate it using _eval_, it does what you would expect:
|
|
|
|
[minimal_template_evaluation]
|
|
|
|
[note There is a reference model of the _ExprTmpl_ concept that comes with _yap_:
|
|
_expr_. It has all the member operators defined, and the nonmember operators
|
|
and the `if_else` pseudo-operator are defined in their repsective headers.
|
|
|
|
_expr_ is great for prototyping, and for quick-and-dirty uses of _ets_, but
|
|
its use of every single operator makes it a suboptimal choice for most use
|
|
cass.]
|
|
|
|
[note Because _yap_ operates on _Exprs_, it is possible to mix and match
|
|
_Exprs_ that are instantiations of different templates. An expression
|
|
containing an _expr_ subexpression and subexpressions instantiated from N
|
|
other _ExprTmpls_ can be passed as an argument to any of the _Expr_-accepting
|
|
_yap_ functions.]
|
|
|
|
One more thing. It is important to remember that _yap_ expressions are
|
|
all-lazy, all the time. There is no auto-evaluation of a _yap_ expression
|
|
like there was with Proto. If you want your expressions to be evaluated, you
|
|
must call _eval_ or _eval_as_, enable _conv_op_m_ (a dubious solution), or
|
|
define non-lazy operations that force evaluation where and when you want it.
|
|
This last approach is usually the right one, and there are lots of examples of
|
|
how to do this in the Examples section.
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Kinds of Expressions]
|
|
|
|
There are four special kinds of expression.
|
|
|
|
[variablelist Special Expression Kinds
|
|
[[_terminal_] [A terminal contains a non-Expression value, and represents the leaf-node in an
|
|
expression tree.]]
|
|
[[_placeholder_] [A placeholder is a terminal that acts as a stand-in for some value to be provided later.]]
|
|
[[_if_else_] [An `if_else` expression is analogous to the C++ ternary operator (`?:`), but
|
|
without the common-type conversion logic.]]
|
|
[[_expr_ref_] [An `expr_ref` expression is one that acts as a (possibly `const`) lvalue reference to another expression. It exists to prevent unnecessary copies of expressions.]]
|
|
]
|
|
|
|
The other expression kinds are all the overloadable operators, See _kind_ for
|
|
the full list.
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Operators]
|
|
|
|
Let's see an expression template type with some operators:
|
|
|
|
[lazy_vector_decl]
|
|
|
|
Those macros are used to define operator overloads that return _Exprs_. As
|
|
shown here, that sort of operator can be mixed with normal, non-lazy ones.
|
|
|
|
Use of the macros is not necessary (you can write your own operators that
|
|
return _Exprs_ if you like), but it is suitable 99% of the time.
|
|
|
|
Making the operators easy to define like this allows you to define custom
|
|
expression templates that have only the operators defined that are appropriate
|
|
for your use case.
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Operator Macros]
|
|
|
|
There are macros that define binary operators as members and macros that
|
|
define them as nonmembers. For a given operator, the member-defining macro
|
|
will define an operator that accepts anything on the right (making a terminal
|
|
out of a non-_Expr_ operand); the nonmember-defining macro will define an
|
|
operator that will only accept a non-_Expr_ on the left and an _Expr_ on the
|
|
right. This is to avoid creating ambiguous overload sets. Here are the
|
|
macros and their uses:
|
|
|
|
[table Unary and Binary Operator-Defining Macros
|
|
[[Macro] [Use] [First/Left Operand Type] [Right Operand Type] [Notes]]
|
|
|
|
[[_unary_member_m_] [Unary member operators.] [Any type.] [--] []]
|
|
[[_binary_member_m_] [Binary member operators.] [Any type.] [Any type.] []]
|
|
[[_binary_free_m_] [Binary free operators.] [Any non-_Expr_.] [Any _Expr_.] []]
|
|
[[_udt_unary_m_] [Free operators defined over types constrained by a type trait (e.g. all `std::map<>`s).] [Any non-_Expr_ that satisfies the given type trait.] [--] []]
|
|
[[_udt_udt_binary_m_] [Free operators defined over types constrained by a pair of type traits (e.g. a `std::map<>` on the left, and a `std::vector<>` on the right). Useful for type-asymmetric operators.] [Any non-_Expr_ that satisfies the left-hand type trait.] [Any non-_Expr_ that satisfies the right-hand type trait.] []]
|
|
[[_udt_any_binary_m_] [Free operators defined over pairs of types, one constrained by a type trait and one not (e.g. a `std::list<>` on either side, and anything on the other).] [Any non-_Expr_.] [--] [At least one parameter must satisfy the given type trait.]]
|
|
]
|
|
|
|
[table The Other Operator-Defining Macros
|
|
[[Macro] [Use] [Operands] [Notes]]
|
|
|
|
[[_member_call_m_] [Member call operator.] [Any type.] []]
|
|
[[_expr_if_else_m_] [Free `if_else()` function that requires at least one parameter to be an expression.] [Any type.] [At least one parameter must be an _Expr_.]]
|
|
[[_udt_any_if_else_m_] [Free `if_else()` function that requires at least one parameter to satisfy the given type trait.] [Any non-_Expr_.] [At least one parameter must satisfy the given type trait.]]
|
|
]
|
|
|
|
[note Operands are handled in a uniform way across all functions defined by
|
|
all the macros listed here. See _how_treated_ for details.]
|
|
|
|
[endsect]
|
|
|
|
|
|
[section How Expression Operands Are Treated]
|
|
|
|
For any _expr_ operator overload, or any function defined using one of the
|
|
function definition macros, operands are treated in a uniform way.
|
|
|
|
The guiding design principle here is that an expression built using _yap_
|
|
should match the semantics of a builtin C++ expression as closely as possible.
|
|
This implies that an rvalue remain treatable as a temporary (as it may in fact
|
|
be) throughout the building and transformation of an expression, and that an
|
|
lvalue should retain its connection to the underlying named entity to which it
|
|
refers.
|
|
|
|
For example, if you see
|
|
|
|
auto expr = a + 1;
|
|
|
|
you should expect that `a` will be an lvalue reference to some object of type
|
|
`decltype(a)`, regardless of whether `a` is an _Expr_ or a builtin type.
|
|
Similarly, you should expect the `1` to be an rvalue, whether wrapped in a
|
|
terminal or not.
|
|
|
|
Let's take a quick look at _make_term_. If you call it with a `T` rvalue, the
|
|
terminal's value type is a `T`, and the rvalue gets moved into it. If you
|
|
call it with a `T [const]` lvalue, the value type is `T [const] &`, and the
|
|
reference refers to the lvalue (read `[const]` as "possibly
|
|
`const`-qualified"). This is important because you might write through the
|
|
terminal later in an assignment operation. You don't want to lose the ability
|
|
to do this, or be forced to write some Baroque pile of code to do so -- it
|
|
should be natural and easy.
|
|
|
|
And it is:
|
|
|
|
[assign_through_terminal]
|
|
|
|
Now, there is a wrinkle. _yap_'s lazy expressions can be built piecemeal:
|
|
|
|
auto subexpr = boost::yap::make_terminal(1) + 2;
|
|
// This is fine, and acts more-or-less as if you wrote "1 / (1 + 2)".
|
|
auto expr = 1 / subexpr;
|
|
|
|
whereas C++'s eager builtin expressions cannot:
|
|
|
|
auto subexpr = 1 + 2; // Same as "int subexpr = 3;". Hm.
|
|
auto expr = 1 / subexpr; // Same as "int expr = 0;" Arg.
|
|
|
|
Ok, so since you can build these lazy _yap_ expressions up from
|
|
subexpressions, how do we treat the subexpressions? We treat them in exactly
|
|
the same way as _make_term_ treats its operands. Rvalues are moved in, and
|
|
lvalues are captured by (possibly `const`) reference.
|
|
|
|
[note If you want to subvert the capture-by-reference semantics of using
|
|
subexpressions, just `std::move()` them. That will force a move -- or copy of
|
|
values for which move is not defined.]
|
|
|
|
The capture-by-reference behavior is implemented via a special _kind_,
|
|
_expr_ref_. An `expr_ref` expression has a single data element: a (possibly
|
|
`const` (Can I stop saying that every time? You get it, right? Ok, good.))
|
|
reference to an expression. This additional level of indirection causes some
|
|
complications at times, as you can see in the examples. Fortunately, the
|
|
complications are not overly cumbersome.
|
|
|
|
So, given the rules so far, here is a comprehensive breakdown of what happens
|
|
when an operand is passed to a _yap_ operator. In this table, `expr` is an
|
|
_ExprTmpl_, and `T` is a non-_Expr_ type. `E` refers to any non-`expr_ref`
|
|
_Expr_. _yap_ does a partial decay on non-_Expr_ operands, in which `cv` and
|
|
reference qualifiers are left unchanged, but arrays are decayed to pointers
|
|
and functions are decayed to function pointers. `PARTIAL_DECAY(T)` indicates
|
|
such a partial decay of `T`.
|
|
|
|
[table Operand Handling
|
|
[[Operand] [Captured As] [Notes]]
|
|
|
|
[[`T const &`] [`expr<expr_kind::terminal, boost::hana::tuple<PARTIAL_DECAY(T) const &>>`] []]
|
|
[[`T &`] [`expr<expr_kind::terminal, boost::hana::tuple<PARTIAL_DECAY(T) &>>`] []]
|
|
[[`T &&`] [`expr<expr_kind::terminal, boost::hana::tuple<PARTIAL_DECAY(T)>>`] [Operand moved.]]
|
|
|
|
[[`E const &`] [`expr<expr_kind::expr_ref, boost::hana::tuple<E const &>>`] []]
|
|
[[`E &`] [`expr<expr_kind::expr_ref, boost::hana::tuple<E &>>`] []]
|
|
[[`E &&`] [`E`] [Operand moved.]]
|
|
|
|
[[`expr<expr_kind::expr_ref, ...> const &`] [`expr<expr_kind::expr_ref, ...>`] []]
|
|
[[`expr<expr_kind::expr_ref, ...> &`] [`expr<expr_kind::expr_ref, ...>`] []]
|
|
[[`expr<expr_kind::expr_ref, ...> &&`] [`expr<expr_kind::expr_ref, ...>`] [Operand moved.]]
|
|
]
|
|
|
|
The partial decay of non-_Expr_ operands is another example of how _yap_
|
|
attempts to create expression trees that are as semantically close to builtin
|
|
expressions as possible.
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Customization Points]
|
|
|
|
There are a number of _cps_ defined by _yap_ that are used during expression
|
|
evaluation. _yap_ _cps_ are patterned after the ones in the _range_v3_
|
|
library, the basis for the ISO Ranges TS. To override one, you create a
|
|
function with the same name in your own namespace; it gets picked up via ADL.
|
|
By creating your own version of one of these _cps_, you can change how
|
|
expressions are evaluated in _eval_ and _eval_as_.
|
|
|
|
For example: first we define a UDT `number` and an override of _yap_'s
|
|
`eval_plus` for it,
|
|
|
|
[eval_plus_example_decls]
|
|
|
|
then we use it to evaluate expressions containing `number`s, even though there
|
|
is no user-defined plus operator for `number`:
|
|
|
|
[eval_plus_example_use]
|
|
|
|
There is a _cp_ for each operator (including `if_else`), one for doing
|
|
implicit transforms during evaluation, called `transform_expression()`, and
|
|
yet one more for replacing the entire implementation of _eval_as_, called
|
|
`eval_expression_as()`. The complete list of the _cps_ can be found in the
|
|
`function_objects` namespace in the _ops_header_.
|
|
|
|
You just saw an example of an operator _cp_ above. Here's an example
|
|
overriding the `transform_expression()` _cp_:
|
|
|
|
[transform_expression_example_decls]
|
|
|
|
and using it:
|
|
|
|
[transform_expression_example_use]
|
|
|
|
The `eval_expression_as()` _cp_ works similarly.
|
|
|
|
You can create as many overloads of a _cp_ `C` as you like. If there are 100
|
|
overloads of `C`, any of them that match subexpressions within the expression
|
|
being evaluated will be used; you only need to make sure that the overloads
|
|
are mutually unambiguous.
|
|
|
|
This is especially important when it comes to the `transform_expression()`
|
|
_cp_. Providing multiple overloads can be very useful for defining an
|
|
implicit set of transformations that you always want applied to all your
|
|
expression evaluations. For example, if you define a template overload and a
|
|
nontemplate overload for a certain _cp_ `C`, the template will only apply when
|
|
the nontemplate does not (due to the normal C++ overload resolution rules);
|
|
this allows general-casing in the template overload, and special-casing in the
|
|
nontemplate overload.
|
|
|
|
[heading How the transform_expression Customization Point Is Applied]
|
|
|
|
If at any point during the execution of _eval_ or _eval_as_, if `expr` is the
|
|
current subexpression and `transform_expression(expr)` is well-formed,
|
|
`transform_expression(expr)` gets called in place of the normal evaluation of
|
|
`expr`.
|
|
|
|
[important Subexpressions of `expr` are not evaluated recursively, and in fact
|
|
they are not evaluated at all! The arguments passed to
|
|
`transform_expression()` are _Exprs_, not evaluated values.]
|
|
|
|
If you want recursive evaluation within your `transform_expression()`
|
|
override, just use `evaluate()` directly:
|
|
|
|
This transform takes expressions of the form `a * x + y` and calls a function
|
|
that does that computation in one step:
|
|
|
|
[naxpy_transform_decl]
|
|
|
|
and since `evaluate()` is called on each operand in the transform, the
|
|
transform is applied to each parenthesized subexpression here, and then to the
|
|
top-level expression:
|
|
|
|
[naxpy_transform_use]
|
|
|
|
[heading How the eval_expression_as Customization Point Is Applied]
|
|
|
|
The `eval_expression_as()` _cp_ is only applied at the top level, replacing a
|
|
call to _yap_'s version. Like `transform_expression()`, it subverts the
|
|
normal recursive evaluation of the expression being evaluated. Also like
|
|
`transform_expression()`, it is passed an _Expr_.
|
|
|
|
`eval_expression_as()` may be useful if you want to evaluate an expression in
|
|
a very different way depending on what the desired result type is.
|
|
|
|
[note Evaluation of _expr_'s optional conversion operator template is done via
|
|
`eval_expression_as()`. See _conv_op_m_ for details.]
|
|
|
|
[heading How The Other Customization Points Are Applied]
|
|
|
|
Each of _eval_ and _eval_as_ evaluates its given expression recursively, depth
|
|
first. For any given subexpression `E` of kind `K` with subexpressions `S0`
|
|
... `Sn`, `E` is evaluated as:
|
|
|
|
eval_K(evaluate(S0), ... evaluate(Sn))
|
|
|
|
For example, using `expr_kind::plus`:
|
|
|
|
eval_plus(evaluate(left(e)), evaluate(right(e)))
|
|
|
|
This means that a (sufficiently general) templated `eval_plus()` _cp_ override
|
|
would apply to all plus operations in an expression tree, even nested ones.
|
|
|
|
All the _cps_ except `transform_expression()` and `eval_expression_as()`
|
|
receive evaluated values in their arguments, not _Exprs_. That is, unless
|
|
some of the evaluated values happen to be _Exprs_ -- you may have written an
|
|
expression that evaluates to an expression. You may also be a weirdo.
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Transform Matching]
|
|
|
|
In _yap_ a _XForm_ is a _Callable_ that has *zero or more* overloads that
|
|
model the _ExprXForm_ or _TagXForm_ concepts.
|
|
|
|
An _ExprXForm_ overload takes the expression to be transformed:
|
|
|
|
[expr_xform]
|
|
|
|
_ExprXForms_ are most useful when you want to transform a narrow set of
|
|
expression types (perhaps only one). In particular, you can distinguish
|
|
between `const` and non-`const`, reference and non-reference, etc., in the
|
|
expression and its operands in a way that you have less control over with the
|
|
other kind of transform.
|
|
|
|
A _TagXForm_ overload takes a tag that indicates the _kind_ of the expression
|
|
to be transformed, and then (loosely) the value of each operand of the
|
|
epxression to be transformed:
|
|
|
|
[tag_xform]
|
|
|
|
_TagXForms_ are most useful when the transform needs to match an expression
|
|
without regard to whether its operands are _expr_ref_ expressions, or -- if
|
|
they are terminals -- whether they contain or refer to their values.
|
|
_TagXForms_ tend to be far more concise.
|
|
|
|
[heading A More Rigorous Description of TagTransform Parameters]
|
|
|
|
That "(loosely)" before probably bothered you, right? Me too. Each non-tag
|
|
parameter is passed to a _TagXForm_ by calling an operand accessor appropriate
|
|
to `expr`'s kind, and then calling _value_ on the result. For example,
|
|
consider a plus expression `expr`. The _TagXForm_ on a transform object
|
|
`xform` would be called like this:
|
|
|
|
xform(plus_tag, value(left(expr)), value(right(expr)))
|
|
|
|
The operand accessors (_left_ and _right_ in this example) all dereference
|
|
_expr_ref_ expressions before operating on them, and _value_ does the same.
|
|
When given a terminal, _value_ also returns the value of the terminal.
|
|
|
|
The result of this pattern is that you can effectively ignore the presence of
|
|
_expr_ref_ expressions when writing a _TagXForm_. You can also just deal with
|
|
the values inside terminals, and not the terminals themselves. Also, you can
|
|
match all terminal value qualifiers (`const` or not, lvalue or rvalue)
|
|
uniformly with a `T const &` parameter. Finally, you can write _TagXForm_
|
|
parameter types that can catch conversions; for instance, you can match any
|
|
negation expression containing a terminal, *or a reference to one*, containing
|
|
a value convertible to `double` like this:
|
|
|
|
struct xform
|
|
{
|
|
auto operator() (boost::yap::negate_tag, double x)
|
|
{ return /* ... */; }
|
|
}
|
|
|
|
That will match a negation of a terminal containing an `unsigned int`,
|
|
`unsigned int &`, `int const &`, `float &&`, etc. It will also match a
|
|
negation of a reference to such a terminal.
|
|
|
|
[heading Mixing the Two Kinds of Transforms]
|
|
|
|
You can have two overloads in your transform that match an expression, one an
|
|
_ExprXForm_ and one a _TagXForm_, and there will not be any ambiguity. The
|
|
_TagXForm_ is prefered, except in the case of a call expression, in which case
|
|
the _ExprXForm_ is preferred. I know, I know. You don't have to worry about
|
|
amiguity, but save youself some confusion and mix the two kinds of overloads
|
|
as little as possible.
|
|
|
|
[endsect]
|
|
|
|
|
|
[endsect]
|