• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1[section An Expression Template Primer]
2
3What are _ets_ anyway?  In short, _ets_ are templates that you write to
4capture expressions so that they can be transformed and/or evaluated lazily.
5
6An example of normal C++ expression is:
7
8    std::sqrt(3.0) + 8.0f
9
10The compiler sees this and creates some representation of that expression
11inside the compiler.  This is typically an _ast_ (AST).  The AST for the
12expression above might be:
13
14[$yap/img/ast.png]
15
16This tree structure captures all the elements of the original C++ code.  The
17expression is a plus operation whose left side is a call to `std::sqrt(3.0)`
18and whose right side is `8.0f`.  The call to `std::sqrt(3.0)` is its own
19expression subtree consisting of a call node and its argument node.
20
21A _yap_ version of this same tree is:
22
23[$yap/img/expr.png]
24
25The `operator+()` is represented by a _yap_ expression whose kind is
26`yap::expr_kind::plus` and the call is represented by a _yap_ expression whose
27kind is `yap::expr_kind::call`.  Notice that the call expression has two
28terminals, one for the callable, and one for its single argument.
29
30The type that holds this expression is:
31
32[plus_sqrt_yap_type]
33
34That looks like a big mess; let's unpack it.  You might notice that the
35overall shape is the same as the expression tree diagram above.  We have
36tree-like nesting of `boost::yap::expression` template instantiations.
37
38Here's the top-level `boost::yap::expression` again with
39its noisy guts removed:
40
41[plus_sqrt_yap_top_level_1]
42
43    // Left and right operand expressions ...
44
45[plus_sqrt_yap_top_level_2]
46
47It has an _kind_ of `plus` as its first template parameter (it's a non-type
48parameter); this indicates what kind of "node" it is.  In this case, the top
49level expression is analogous to our `operator+()` AST node.  Its operands are
50the elements of its _tuple_ data member.
51
52The left operand to the top-level plus operation is itself a _yap_ expression
53representing `std::sqrt(3.0)`:
54
55[plus_sqrt_yap_lhs]
56
57This expression is a call expression.  The first operand to the call
58expression is the callable entity, in this case a pointer to `std::sqrt`.  The
59remaining operands are the arguments to pass to the callable; in this case,
60there's only one operand after the callable, `3.0`.
61
62The children of the `std::sqrt(3.0)` subexpression are terminals.  This means
63that they are leaf nodes in our notional AST.
64
65The right operand to the top-level plus operation is of course also a _yap_
66expression.  It is also a terminal:
67
68[plus_sqrt_yap_rhs]
69
70Notice a couple of things here: 1) non-terminals (the top-level plus operation
71and the call opertion in our example) have tuple elements that are *all* _yap_
72expressions, and 2) terminals have tuple elements, *none of which* are _yap_
73expressions (they're just normal types like `float` and `double (*)(double)`).
74
75[note From here on, I'll use the terms "expression" and "node" interchangably,
76and I'll also use the terms "subexpression" and "child" interchangably.  Even
77though _ets_ are not identical to tree-based ASTs, they're close enough that
78the terminology is interchangable without loss of meaning.]
79
80[heading Capturing an Expression]
81
82If we want to capture an expression using _yap_ we have to do something to let
83the compiler know not just to eagerly evaulate our expression, as it does when
84it sees `std::sqrt(3.0) + 8.0f`.
85
86To do this, we create _terminal_ expressions out of one or more of the
87terminals in the expression we want to capture and evaluate lazily.  Here,
88I've declared a template alias to make that easier to type:
89
90[plus_sqrt_term_alias]
91
92And here is how I might use that alias to create the terminal containing
93`std::sqrt`:
94
95[plus_sqrt_yap_value]
96
97The reason I can then just call the terminal with a `3.0` argument and add
98`8.0f` to the result is that I'm taking a great big shortcut in this example
99by using _yap_'s built-in example _et_, _expr_.  _expr_ is a template with all
100the operator overloads defined, including the call operator.  Each operator
101overload returns an _expr_, which is why the `+` in `std::sqrt(3.0) + 8.0f`
102also works.
103
104[note _expr_ is great for example code like what you see here, and it's great
105for small _et_ use cases that are essentially implementation details.  You
106should write your own _ets_ for anything that is to be used in any other
107context.  The reason for this is that most of the time your _et_ system will
108not want to support all combinations of all possible operators and function
109calls.  For instance, code like this:
110
111    (a + b) = c;
112
113is at least unusual, if not outright wrong.  Where does `c` go?  Into `a`,
114`b`, or into an expiring `a + b` temporary?  What if `a` is a `std::string`
115and `b` is a `FILE *`?  _expr_ doesn't care.  You probably want to design
116interfaces that are more carefully considered than the "everything goes" style
117implied by using _expr_.  ]
118
119_yap_ comes with a handy _print_ function.  Calling it like this:
120
121[print_plus_sqrt_yap_value]
122
123Gives this output:
124
125    expr<+>
126        expr<()>
127            term<double (*)(double)>[=1]
128            term<double>[=3]
129        term<float>[=8]
130
131This is a lot more readable.  I show this to you here to give you a more
132concise view of the AST-like structure.
133
134(In case you're wondering why `&std::sqrt` is printed as the value `1`, so was
135I.  Apparently, that's just what GCC prints for that.  Weird.)
136
137[heading Doing Something Useful With It]
138
139Now we've seen a simple expression both described as a C++ AST and captured as
140a _yap_ expression.  This just introduces the _et_ mechanism; what do we do
141with it once we have an _et_?  Consider one of the examples from the intro:
142
143    std::vector<int> v1 = {/* ... */};
144    std::vector<int> v2 = sort(v) | unique;
145
146The rest of the tutorial will explain in greater detail how _yap_ can be used
147in situations like this, but the brief version is this:
148
149* Use _yap_ to capture an expression.  In this case, something like `auto expr
150  = sort(v) | unique;`.
151
152* Use the _yap_ _xform_ algorithm to transform the expression into what you
153  want.  In this case, something like `auto desired_expr =
154  yap::transform(expr, my_transform);`, which turns the concise form `sort(v)
155  | unique` into the more verbose calls required by the standard algorithm
156  APIs.  Note that the resulting expression can be transformed repeatedly if
157  this is desirable.
158
159* Evauate the final expression, either using _eval_ or a call to _xform_ that
160  transforms the final expression into an evaluated result.
161
162[endsect]
163