From: David Abrahams (dave_at_[hidden])
Date: 2005-11-04 12:59:24
Joel de Guzman <joel_at_[hidden]> writes:
> Comments, feedback very welcome!
> Lazy operators...
What is this doing here? What is the ellipsis for?
> This facility provides a mechanism for lazily evaluating
> operators. Syntactically, a lazy operator looks and feels like an
> ordinary C/C++ infix, prefix or postfix operator. The operator
> application looks the same. However, unlike ordinary operators, the
> actual operator execution is deferred. Samples:
> arg1 + arg2
> 1 + arg1 * arg2
> 1 / -arg1
> arg1 < 150
> We have seen the lazy operators in action (see Quick Start). Let's
> go back and examine it a little bit further:
> find_if(c.begin(), c.end(), arg1 % 2 == 1)
> Through operator overloading, the expression arg1 % 2 == 1 actually
> generates a composite.
The fact that it's a composite (as opposed to a primitive) is of no
interest at all to the user at this point. I would say "a function
object" or "an actor." There's way too much emphasis on this whole
> This composite object is passed on to STL's
> find_if function.
This is a place where you really see how not using any qualification
hurts. Should be std::find_if, to distinguish it from the library's
lazy find_if that can be passed to higher-order functions.
> In the viewpoint of STL, the composite is simply a
> function object expecting a single argument
> , the container's element.
"of the container's value_type."
> For each element in the container c, the element is passed
"Each element in c"
> on as an argument arg1 to the composite (function object). The
strike. That is not the name of the argument.
> composite checks if this is an odd value based on the expression
> arg1 % 2 == 1 where arg1 is iteratively replaced by the container's
> A set of classes implement all the C++ free operators.
Really, the operators are implemented by a set of classes? Seems
bizarre. Also seems irrelevant to the reader.
> Like lazy functions (see function), lazy operators are not
> immediately executed when invoked. Instead, a composite (see
> composite) object is created and returned to the caller. Example:
> (arg1 + arg2) * arg3
> does nothing more than return a composite. A second function call will evaluate the actual operators. Example:
> int i = 4, j = 5, k = 6;
> cout << ((arg1 + arg2) * arg3)(i, j, k);
> will print out "54".
> Arbitrarily complex expressions can be lazily evaluated following
> three simple rules:
That sentence doesn't make sense to me. "Can be" would apply to what
the user can do, but the rules describe what the library does. Maybe
"...are lazily evaluated according to these three rules:"
I'm not sure that "Arbitrarily complex" adds anything. It would make
sense in the context of what the user can do:
"Arbitrarily complex epressions can be made lazy by
but then, that's incompatible with what follows.
> 1. Lazy evaluated binary operators apply when at least one of the
> operands is an actor object (see actors, primitives and
> composite). Consequently, if one of the operands is not an actor
> object, it is implicitly converted, by default, to a value (see
> 2. Lazy evaluated unary operators apply only to operands which
> are actor objects.
> 3. The result of a lazy operator is a composite actor object that
> can in turn apply to rule 1 and 2.
The actor applies to the rules, or the rules apply to the actor?
This may seem like nitpicking, but the above can only make sense if
you already know what it's saying, because you'll overlook the logical
inconsistencies. It won't help the person who's trying to understand
the library from the outside.
> The one exception is the member pointer operator ->*, as we shall
> see later.
As long as you're spelling out the rules in detail, you should add the
one exception immediately, so the user can read them all in one place.
> -(arg1 + 3 + 6)
> 1. Following rule 1, lazy evaluation is triggered since arg1 is
> an actor (see primitives).
You have to say what part of the expression is being evaluated!
In this case,
arg1 + 3
> 2. The immediate right operand: 3 is implicitly converted to a
> value. Still following rule 1.
> 3. The result of this arg1 + 3 expression is a composite object,
> following rule 3.
> 4. Now since arg1 + 3 is a composite, following rule 1 again, its
> right operand: 6 is implicitly converted to a value.
> 5. Continuing, the result of arg1 + 3 ... + 6 is again another
> composite. Rule 3.
> 6. The expression arg1 + 3 + 6 being a composite, is the operand
> of the unary operator -. Following rule 2, the result is an actor
> 7. Following rule 3, the whole thing -(arg1 + 3 + 6) is a composite.
If so, it's because you're exposing way more detail than the user
needs, IMO. All this stuff could be better explained by a section
How to know when the result of an expression is an actor
> well, when in doubt, just wrap the operands in ref(x), val(x) or
> cref(x) appropriately and you'll be ok:
> -(arg1 + val(3) + val(6))
How does the above help unconfuse the reader?
> Lazy-operator application is highly contagious. In most cases, a
> single argN actor infects all its immediate neighbors within a group
> (first level or parenthesized expression).
> Take note that although at least one of the operands
> must be a valid actor class in order for lazy evaluation to take
> effect, if this is not the case and we still want to lazily evaluate
> an expression, we can use ref(x), val(x) or cref(x) to transform the
> operand into a valid action object (see primitives). Example:
> val(1) << 3;
Note that at least one operand of any operator must be a valid actor
for lazy evaluation to take effect. To force lazy evaluation of an
ordinary expression, we can use ref(x), val(x) or cref(x) to
transform an operand into a valid actor object. For example,
val(1) << 3;
-- Dave Abrahams Boost Consulting www.boost-consulting.com
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk