PrevUpHomeNext

How Expression Operands Are Treated

For any expression<> 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 Boost.YAP should match the semantics of a builtin C++ expression as closely as possible. This implies that an rvalue be treated as if it were a temporary (as it may in fact have initially been) 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 a Boost.YAP Expression 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_terminal(). 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:

int i = 0;
auto expr = boost::yap::make_terminal(i) = 42;
evaluate(expr);
std::cout << i << "\n"; // Prints 42.

Now, there is a wrinkle. Boost.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 Boost.YAP expressions up from subexpressions, how do we treat the subexpressions? We treat them in exactly the same way as make_terminal() treats its parameter. Rvalues are moved in, and lvalues are captured by (possibly const) reference.

[Note] 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 expr_kind, expr_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 above, here is a comprehensive breakdown of what happens when an operand is passed to a Boost.YAP operator. In this table, expr_tmpl is an ExpressionTemplate, and T is a non-Expression type. E refers to any non-expr_ref Expression. Boost.YAP does a partial decay on non-Expression 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 1.4. Operand Handling

Operand

Captured As

Notes

T const &

expr_tmpl<expr_kind::terminal, boost::hana::tuple<PARTIAL_DECAY(T)>>

T &

expr_tmpl<expr_kind::terminal, boost::hana::tuple<PARTIAL_DECAY(T)>>

T &&

expr_tmpl<expr_kind::terminal, boost::hana::tuple<PARTIAL_DECAY(T)>>

Operand moved.

E const &

expr_tmpl<expr_kind::expr_ref, boost::hana::tuple<E const &>>

E &

expr_tmpl<expr_kind::expr_ref, boost::hana::tuple<E &>>

E &&

E

Operand moved.

expr_tmpl<expr_kind::expr_ref, ...> const &

expr_tmpl<expr_kind::expr_ref, ...>

expr_tmpl<expr_kind::expr_ref, ...> &

expr_tmpl<expr_kind::expr_ref, ...>

expr_tmpl<expr_kind::expr_ref, ...> &&

expr_tmpl<expr_kind::expr_ref, ...>

Operand moved.


The partial decay of non-Expression operands is another example of how Boost.YAP attempts to create expression trees that are as semantically close to builtin expressions as possible.


PrevUpHomeNext