[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>`] []] [[`T &`] [`expr>`] []] [[`T &&`] [`expr>`] [Operand moved.]] [[`E const &`] [`expr>`] []] [[`E &`] [`expr>`] []] [[`E &&`] [`E`] [Operand moved.]] [[`expr const &`] [`expr`] []] [[`expr &`] [`expr`] []] [[`expr &&`] [`expr`] [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]