mirror of
https://github.com/boostorg/yap.git
synced 2026-02-22 16:02:10 +00:00
235 lines
10 KiB
Plaintext
235 lines
10 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. `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<DECAY(T) const &>>`] []]
|
|
[[`T &`] [`expr<expr_kind::terminal, boost::hana::tuple<DECAY(T) &>>`] []]
|
|
[[`T &&`] [`expr<expr_kind::terminal, boost::hana::tuple<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]
|
|
|
|
[endsect]
|
|
|
|
|
|
[section Transform Matching]
|
|
|
|
[endsect]
|
|
|
|
|
|
[endsect]
|