2
0
mirror of https://github.com/boostorg/yap.git synced 2026-02-22 16:02:10 +00:00
Files
yap/doc/tutorial.qbk
2016-12-13 17:06:26 -06:00

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]