Forming CLEF expressions
In this section, we describe how to form CLEF expressions.
Placeholders
Loosely speaking, a placeholder is a “variable name” used to build an expression. Placeholders are declared as
placeholder<Number> Name;
Example
placeholder <1> x_;
placeholder <2> y_;
Note that the only thing of significance in a placeholder is its type (i.e. a number). A placeholder is empty: it contains no value at runtime.
Warning
As a consequence, defining
placeholder <1> y_;would imply that x_ is the same as y_: x_ == y_ will be always true.
Forming an expression
CLEF expressions are made of:
Placeholders
Binary operations on expressions (+, -, *, /, >, <, >=, <=, ==)
Ternary conditional if_else expressions
Callable objects which overload the operator () for CLEF expressions, See Overloading operator() and other methods.
Functions overloaded for CLEF expressions. For example, the header math.hpp contains the declaration to make the basic function of std math.h accept CLEF_expressions.
In fact, almost anything: the make_expr function can be called on any object to make it lazy.
Examples:
#include <nda/clef.hpp>
#include <vector>
using namespace nda::clef;
int main() {
placeholder<0> i_;
placeholder<1> x_;
placeholder<2> y_;
std::vector<int> V;
// arithmetic
auto e = x_ + 2 * y_;
// simple math function
auto e1 = cos(2 * x_ + 1);
auto e2 = abs(2 * x_ - 1);
// making V lazy
auto e0 = make_expr(V)[i_];
}
Note that:
Expressions do not compute anything, they just store the expression tree.
There is no check of correctness here in general: an expression can be well formed, but meaningless, e.g.
auto e = cos(2*x_, 8); // !
Storage of expressions [advanced]
CLEF expressions have a complicated (expression template) type encoding the structure of the expression at compile time:
auto e = x_ + 2* y_;
// the type of e is something like
expr<tags::plus, placeholder<1>, expr<tags::multiplies, int, placeholder<2> >
Note that:
As a user, one never has to write such a type. One always use expression “on the fly”, or use auto.
Having the whole structure of the expression at compile time allows efficient evaluation (it is the principle of expression template: add a ref here).
Declaring an expression does not do any computation. It just stores the expression tree (its structure in the type, and the leaves of the tree).
Every object in the expression tree is captured by :
documentation/manual/triqs it is an lvalue.
value it is an rvalue: an rvalue (i.e. a temporary) is moved into the tree, using move semantics.
Exceptions: the following objects are always copied: placeholders, expression themselves.
Example
double a = 3; auto e = a + 2* x_ ; // a is stored by documentation/manual/triqs (double &), but 2 is stored by value
The rationale is as follows:
rvalue must be moved, otherwise we would keep (dangling) documentation/manual/triqs to temporaries.
for lvalue, keeping a documentation/manual/triqs is quicker. Of course, in the previous example, it is mandatory that a live longer than e …