⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 phoenix_users_manual.txt

📁 boost库提供标准的C++ API 配合dev c++使用,功能更加强大
💻 TXT
📖 第 1 页 / 共 5 页
字号:

Lazy variables are actors. As such, variables can be evaluated through the actor's operator(). Such invocation gives the variables's identity. Example:

    int i = 3;
    char const* s = "Hello World";
    cout << var(i)() << var(s)();

prints out "3 Hello World"

Finally, another free function const_(cv) may also be used. const_(cv) creates an actor<variable<T const&> > object where the data is referenced using a constant reference. This is similar to value<T> but when the data to be passed as argument to a function is heavy and expensive to copy by value, the const_(cv) offers a low overhead alternative.

[page Composites]

Actors may be combined in a multitude of ways to form composites. Composites are actors that are composed of zero or more actors. Composition is hierarchical. An element of the composite can be a primitive or again another composite. The flexibility to arbitrarily compose hierarchical structures allows us to form intricate constructions that model complex functions, statements and expressions.

A composite is more or less a tuple of 0..N actors plus an operation object (some specialized composites have implied operations, i.e. the composite itself implies the operation). The composite class is declared informally as:

    template <
        typename OperationT,
        typename A0 = nil_t,
        typename A1 = nil_t,
        typename A2 = nil_t,
         ...
        typename AN = nil_t
    >
    struct composite {

        OperationT op;              //  operation
        A0 a0; A1 a1; ... AN an;    //  actors
    };

This can be recursive. As mentioned, each of the actors A0..AN can in turn be another composite since a composite is itself an actor superclass and conforms to its expected conceptual interface. Composite specializations are provided to handle different numbers of actors from zero (0) to a predefined maximum.

Except for specialized composites, like the actor and unlike the primitives, the composite is a protocol class. A composite does not really know how to perform anything. The actual operation is handled by its actors and finally its operation 'op'. After it has received the arguments passed in by the actor (see actor), all of the arguments are broadcasted to all of the composite's actors for preprocessing. Each of the composite's actors in turn returns a result. These results are then transfered to the composite's operation 'op'.

If this may seem confusing at first, don't fret. Further details will be provided later for those who are inclined to learn more about the framework inside out. However, such information is not at all required to use the framework. After all, composites are not created directly. Instead, some facilities are provided for the generation of composites. These generators are the front-ends. We have seen the var(x), the val(x) and the const_(x). These are really generators that create primitives. Likewise, we also have generators that create composites.

Just think of composites as your backbone. You don't really have to scrutinize it to use it; it simply works. The composite is indeed the backbone of the Phoenix framework.

[page:1 Functions]

[h2 Lazy functions]

This class provides a mechanism for lazily evaluating functions. Syntactically, a lazy function looks like an ordinary C/C++ function. The function call looks familiar and feels the same as ordinary C++ functions. However, unlike ordinary functions, the actual function execution is deferred. For example here are sample factorial function calls:

    factorial(4)
    factorial(arg1)
    factorial(arg1 * 6 / factorial(var(i)))

These functions are automatically lazily bound unlike ordinary function pointers or functor objects that need to be explicitly bound through the bind function (see binders).

A lazy function works in conjunction with a user defined functor (as usual with a member operator()). Only special forms of functor objects are allowed. This is required to enable true polymorphism (STL style monomorphic functors and function pointers can still be used through the bind facility (see binders)).

This special functor is expected to have a nested template class result<T0...TN> (where N is the number of arguments of its member operator()). The nested template class result should have a typedef 'type' that reflects the return type of its member operator(). This is essentially a type computer that answers the metaprogramming question "Given arguments of type T0...TN, what will be the functor operator()'s return type?".

There is a special case for functors that accept no arguments. Such nullary functors are only required to define a typedef result_type that reflects the return type of its operator().

Here's an example of a simple functor that computes the factorial of a number:

    struct factorial_impl {

        template <typename Arg>
        struct result { typedef Arg type; };

        template <typename Arg>
        Arg operator()(Arg n) const
        { return (n <= 0) ? 1 : n * this->operator()(n-1); }
    };

As can be seen, the functor is polymorphic. Its arguments and return type are not fixed to a particular type. The example above for example, can handle any type as long as it can carry out the required operations (i.e. <=, * and -).

We can now declare and instantiate a lazy 'factorial' function:

    function<factorial_impl> factorial;

Invoking a lazy function 'factorial' does not immediately execute the functor factorial_impl. Instead, a composite object is created and returned to the caller. Example:

    factorial(arg1)

does nothing more than return a composite. A second function call will invoke the actual factorial function. Example:

    int i = 4;
    cout << factorial(arg1)(i);

will print out "24".

Take note that in certain cases (e.g. for functors with state), an instance of the functor may be passed on to the constructor. Example:

    function<factorial_impl> factorial(ftor);

where ftor is an instance of factorial_impl (this is not necessary in this case since factorial is a simple stateless functor). Take care though when using functors with state because the functors are taken in by value. It is best to keep the data manipulated by a functor outside the functor itself and keep a reference to this data inside the functor. Also, it is best to keep functors as small as possible.

[page:1 Operators]

[h2 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:

# 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).

# Lazy evaluated unary operators apply only to operands which are actor objects.

# The result of a lazy operator is a composite actor object that can in turn apply to rule 1.

Example:

    -(arg1 + 3 + 6)

# Following rule 1, lazy evaluation is triggered since arg1 is an instance of an actor<argument<N> > class (see primitives).

# The immediate right operand <3> is implicitly converted to an actor<value<int> >. Still following rule 1.

# The result of this "arg1 + 3" expression is a composite object, following rule 3.

# Now since "arg1 + 3" is a composite, following rule 1 again, its right operand <6> is implicitly converted to an actor<value<int> >.

# Continuing, the result of "arg1 + 3" ... "+ 6" is again another composite. Rule 3.

# The expression "arg1 + 3 + 6" being a composite, is the operand of the unary operator -. Following rule 2, the result is an actor object.

# 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:

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

[page:1 Statements]

[h2 Lazy statements]

The primitives and composite building blocks presented before are sufficiently powerful to construct quite elaborate structures and facilities. We have presented lazy-functions and lazy-operators. How about lazy-statements? First, an appetizer:

Print all odd-numbered contents of an STL container using std::for_each (sample4.cpp):

    for_each(c.begin(), c.end(),
        if_(arg1 % 2 == 1)
        [
            cout << arg1 << ' '
        ]
    );

Huh? Is that valid C++? Read on...

Yes, it is valid C++. The sample code above is as close as you can get to the syntax of C++. This stylized C++ syntax differs from actual C++ code. First, the if has a trailing underscore. Second, the block uses square brackets [] instead of the familiar curly braces {}.

Here are more examples with annotations. The code almost speaks for itself.

[*1) block statement:]

    statement,
    statement,
    ....
    statement

Basically, these are comma separated statements. Take note that unlike the C/C++ semicolon, the comma is a separator put *in-between* statements. This is like Pascal's semicolon separator, rather than C/C++'s semicolon terminator. For example:

    statement,
    statement,
    statement,     //  ERROR!

Is an error. The last statement should not have a comma. Block statements can be grouped using the parentheses. Again, the last statement in a group should not have a trailing comma.

    statement,
    statement,
    (
        statement,
        statement
    ),
    statement

Outside the square brackets, block statements should be grouped. For example:

    for_each(c.begin(), c.end(),
        (
            do_this(arg1),
            do_that(arg1)
        )
    );

[*2) if_ statement:]

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -