Composites revisited

A composite is an actor base class composed of zero or more actors (see actor) and an operation. A composite is itself an actor superclass and conforms to its expected conceptual interface. Its eval member function un-funnels the tupled actual arguments by invoking each of the actors' eval member function. The results of each are then passed on as arguments to the operation. Specializations are provided for composites that handle different numbers of actors from zero to N, where N is a predefined maximum.

Schematically:

    actor0.eval(tupled_args) --> arg0 --> |
    actor1.eval(tupled_args) --> arg1 --> |
    actor2.eval(tupled_args) --> arg3 --> | --> operation(arg0...argN)
    ...                                   |
    actorN.eval(tupled_args) --> argN --> |

Here's a typical example of the composite's eval member function for a 2-actor composite:

    template <typename TupleT>
    typename actor_result<self_t, TupleT>::type
    eval(TupleT const& args) const
    {
        typename actor_result<A0, TupleT>::type r0 = a0.eval(args);
        typename actor_result<A1, TupleT>::type r1 = a1.eval(args);
        return op(r0, r1);
    }

where self_t is the composite's 'self' type, TupleT 'args' is the tuple-packed arguments passed to the actor (see actor) and op is the operation associated with the composite. r0 and r1 are the actual arguments un-funneled from 'args' and pre-processed by the composite's actors which are then passed on to the operation 'op'.

The operation can be any suitable functor that can accept the arguments passed in by the composite. The operation is expected to have a member operator() that carries out the actual operation. There should be a one to one correspondence between actors of the composite and the arguments of the operation's member operator().

The operation is also expected to have a nested template class result<T0...TN>. 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 your operator()'s return type?". There is a special case for operations that accept no arguments. Such nullary operations are only required to define a typedef result_type that reflects the return type of its operator().

Here's a view on what happens when the eval function is called:

    tupled arguments:                 args
                                       |
                       +-------+-------+-------+-------+
                       |       |       |       |       |
                       |       |       |       |       |
    actors:         actor0  actor1  actor2  actor3..actorN
                       |       |       |       |       |
                       |       |       |       |       |
    operation:   op( arg0,   arg1,   arg2,   arg3,...argN )
                  |
                  |
    returns:      +---> operation::result<T0...TN>::type

Here's an example of a simple operation that squares a number:

    struct square {

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

        template <typename ArgT>
        ArgT operator()(ArgT n) const { return n * n; }
    };

This is a perfect example of a polymorphic functor discussed before in the section on functions. As we can see, operations are polymorphic. Its arguments and return type are not fixed to a particular type. The example above for example, can handle any ArgT type as long as it has a multiplication operator.

Composites are not created directly. Instead, there are meta- programs provided that indirectly create composites. See operators, binders and functions for examples.