primer.qbk 6.3 KB

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