Operators

Lazy operators

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 sample2.cpp) above. 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. This composite object is passed on to STL's find_if function. In the viewpoint of STL, the composite is simply a functor expecting a single argument, the container's element. For each element in the container 'c', the element is passed on as an argument (arg1) to the composite (functor). The composite (functor) checks if this is an odd value based on the expression "arg1 % 2 == 1" where arg1 is iteratively replaced by the container's element.

A set of classes implement all the C++ free operators. Like lazy functions (see functions), 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:

  1. Lazy evaluated binary operators apply when *at least* one of the operands is an actor object (see actor, primitives and composite). Consequently, if one of the operands is not an actor object, it is implicitly converted, by default, to an object of type actor<value<T> > (where T is the original type of the operand).
  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.

Example:

    -(arg1 + 3 + 6)
  1. Following rule 1, lazy evaluation is triggered since arg1 is an instance of an actor<argument<N> > class (see primitives).
  2. The immediate right operand <3> is implicitly converted to an actor<value<int> >. 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 an actor<value<int> >.
  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 object.
  7. Folowing rule 3, the whole thing "-(arg1 + 3 + 6)" is a composite.

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 var(x), val(x) or const(x) to transform the operand into a valid action object (see primitives). Example:

    val(1) << 3;

Supported operators:

Unary operators:

    prefix:   ~, !, -, +, ++, --, & (reference), * (dereference)
    postfix:  ++, --

Binary operators:

    =, [], +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
    +, -, *, /, %, &, |, ^, <<, >>
    ==, !=, <, >, <=, >=
    &&, ||