"I like to start documentation with a quote. A nice, pithy one."
— Eric Niebler (paraphrased)
Expression templates are rad. They are used in lots of libraries; here are just three of the most impressive:
However, this can come at a high cost. Expression templates are costly to implement and maintain. Each of Eigen and Boost.Ublas has a large volume of complex expression template code that cannot be reused elsewhere.
With the language facilities available in the C++14 and C++17 standards, an expression template library is now straightforward to write and use, and has very reasonable compile times.
As a quick example, let's say we are doing a bit of matrix math, and we write this statement:
D = A * B + C;
in which all the variables are matrices. It turns out that making a temporary
for A *
B
and then another temporary for
the resulting product plus C
is very inefficient. Most matrix math libraries will have a single function
that does it in one go:
mul_add_assign(D, A, B, C);
If you use a matrix library that offers both kinds of syntax, you have to notice when some bit of operator-using code should be replaced with some more efficient function; this is tedious and error-prone. If the library does not provide the operator syntax at all, only providing the more-efficient function calls, code using the library is a lot less writable and readable.
Using Boost.YAP, you can write some library code that enables expressions like
D =
A * B + C
to be automatically transformed into expressions like mul_add_assign(D,
A, B, C)
.
Consider another example. Many of us have used Unix command line tools to remove duplicate lines in a file:
sort file_with_duplicates | uniq > file_without_duplicates
We can do something very similar with the standard algorithms, of course:
std::vector<int> v1 = {0, 2, 2, 7, 1, 3, 8}; std::sort(v1.begin(), v1.end()); auto it = std::unique(v1.begin(), v1.end()); std::vector<int> const v2(v1.begin(), it); assert(v2 == std::vector<int>({0, 1, 2, 3, 7, 8}));
However, it would be much better if our code did exactly that, but with a more concise syntax:
std::vector<int> v1 = {0, 2, 2, 7, 1, 3, 8}; std::vector<int> const v2 = sort(v1) | unique; assert(v2 == std::vector<int>({0, 1, 2, 3, 7, 8}));
This looks much more similar to the Unix command line above. (Let's pretend that Range-v3 doesn't already do almost exactly this.)
Boost.YAP can be used to do both of these things, in a pretty small amount of code. In fact, you can jump right into the Pipable Algorithms example if you want to see how the second one can be implemented.
evaluate(transform(expr))
idiom is expected to be one of the most common ways of using Yap to manipulate
and evaluate expressions.