Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

Click here to view the latest version of this page.
PrevUpHomeNext

Inside Phoenix

Actors In Detail
Actor Example
Composites In Detail
Composing
Extending

This chapter explains in more detail how the library operates. The information henceforth should not be necessary to those who are interested in just using the library. However, a microscopic view might prove to be beneficial to moderate to advanced programmers who wish to extend the library.

Actor Concept

The main concept is the Actor. Actors are function objects (that can accept 0 to N arguments (where N is a predefined maximum).

[Note] Note

You can set PHOENIX_LIMIT, the predefined maximum arity an actor can take. By default, PHOENIX_LIMIT is set to 10.

actor template class

The actor template class models the Actor concept:

template <typename Eval>
struct actor : Eval
{
    typedef Eval eval_type;

    actor();
    actor(Eval const& base);

    template <typename T0>
    explicit actor(T0 const& _0);

    template <typename T0, typename T1>
    actor(T0 const& _0, T1 const& _1);

    // more constructors

    typename apply_actor<eval_type, basic_environment<> >::type
    operator()() const;

    template <typename T0>
    typename apply_actor<eval_type, basic_environment<T0> >::type
    operator()(T0& _0) const;

    template <typename T0, typename T1>
    typename apply_actor<eval_type, basic_environment<T0, T1> >::type
    operator()(T0& _0, T1& _1) const;

    // function call operators
};

Table 1.10. Actor Concept Requirements

Expression

Result/Semantics

T::eval_type

The actor's Eval type

T()

Default Constructor

T(base)

Constructor from Eval

T(arg0, arg1, ..., argN)

Pass through constructors

x(arg0, arg1, ..., argN)

Function call operators


Eval Concept

The actor template class has a single template parameter, Eval, from which it derives from. While the Actor concept represents a function, the Eval concept represents the function body. The requirements for Eval are intentionally kept simple, to make it easy to write models of the concept. We shall see an example in the next section.

Table 1.11. Eval Concept Requirements

Expression

Result/Semantics

return x.eval(env)

Evaluates the function (see Environment below)

T::result<Env>::type

The return type of eval (see Environment below)


Constructors

In addition to a default constructor and an constructor from a Eval object, there are templated (pass through) constructors for 1 to N arguments (N == PHOENIX_LIMIT). These constructors simply forward the arguments to the base.

[Note] Note

Parametric Base Class Pattern

Notice that actor derives from its template argument Eval. This is the inverse of the curiously recurring template pattern (CRTP). With the CRTP, a class, T, has a Derived template parameter that is assumed to be its subclass. The "parametric base class pattern" (PBCP), on the other hand, inverses the inheritance and makes a class, T, the derived class. Both CRTP and PBCP techniques have its pros and cons, which is outside the scope of this document. CRTP should really be renamed "parametric subclass pattern (PSCP), but again, that's another story.

Function Call Operators

There are N function call operators for 0 to N arguments (N == PHOENIX_LIMIT). The actor class accepts the arguments and forwards the arguments to the actor's base Eval for evaluation.

[Note] Note

Forwarding Function Problem

The function call operators cannot accept non-const temporaries and literal constants. There is a known issue with current C++ called the "Forwarding Function Problem". The problem is that given an arbitrary function F, using current C++ language rules, one cannot create a forwarding function FF that transparently assumes the arguments of F. Disallowing non-const rvalues arguments partially solves the problem but prohibits code such as f(1, 2, 3);.

Environment

On an actor function call, before calling the actor's Eval::eval for evaluation, the actor creates an environment. Basically, the environment packages the arguments in a tuple. The Environment is a concept, of which, the basic_environment template class is a model of.

Table 1.12. Environment Concept Requirements

Expression

Result/Semantics

x.args()

The arguments in a tie (a tuple of references)

T::args_type

The arguments' types in an MPL sequence

T::tie_type

The tie (tuple of references) type


Schematically:

funnel_in

Other parts of the library (e.g. the scope module) extends the Environment concept to hold other information such as local variables, etc.

apply_actor

apply_actor is a standard MPL style metafunction that simply calls the Action's result nested metafunction:

template <typename Action, typename Env>
struct apply_actor
{
    typedef typename Action::template result<Env>::type type;
};

After evaluating the arguments and doing some computation, the eval member function returns something back to the client. To do this, the forwarding function (the actor's operator()) needs to know the return type of the eval member function that it is calling. For this purpose, models of Eval are required to provide a nested template class:

template <typename Env>
struct result;

This nested class provides the result type information returned by the Eval's eval member function. The nested template class result should have a typedef type that reflects the return type of its member function eval.

For reference, here's a typical actor::operator() that accepts two arguments:

template <typename T0, typename T1>
typename apply_actor<eval_type, basic_environment<T0, T1> >::type
operator()(T0& _0, T1& _1) const
{
    return eval_type::eval(basic_environment<T0, T1>(_0, _1));
}

actor_result

For reasons of symmetry to the family of actor::operator() there is a special metafunction usable for actor result type calculation named actor_result. This metafunction allows us to directly to specify the types of the parameters to be passed to the actor::operator() function. Here's a typical actor_result that accepts two arguments:

template <typename Action, typename T0, typename T1>
struct actor_result
{
    typedef basic_environment<T0, T1> env_type;
    typedef typename Action::template result<env_type>::type type;
};

Let us see a very simple prototypical example of an actor. This is not a toy example. This is actually part of the library. Remember the reference?.

First, we have a model of the Eval concept: the reference:

template <typename T>
struct reference
{
    template <typename Env>
    struct result
    {
        typedef T& type;
    };

    reference(T& arg)
        : ref(arg) {}

    template <typename Env>
    T& eval(Env const&) const
    {
        return ref;
    }

    T& ref;
};

Models of Eval are never created directly and its instances never exist alone. We have to wrap it inside the actor template class to be useful. The ref template function does this for us:

template <typename T>
actor<reference<T> > const
ref(T& v)
{
    return reference<T>(v);
}

The reference template class conforms to the Eval concept. It has a nested result metafunction that reflects the return type of its eval member function, which performs the actual function. reference<T> stores a reference to a T. Its eval member function simply returns the reference. It does not make use of the environment Env.

Pretty simple...

We stated before that composites are actors that are composed of zero or more actors (see Composite). This is not quite accurate. The definition was sufficient at that point where we opted to keep things simple and not bury the reader with details which she might not need anyway.

Actually, a composite is a model of the Eval concept (more on this later). At the same time, it is also composed of 0..N (where N is a predefined maximum) Eval instances and an eval policy. The individual Eval instances are stored in a tuple.

[Note] Note

In a sense, the original definition of "composite", more or less, will do just fine because Eval instances never exist alone and are always wrapped in an actor template class which inherits from it anyway. The resulting actor IS-AN Eval.

[Note] Note

You can set PHOENIX_COMPOSITE_LIMIT, the predefined maximum Evals (actors) a composite can take. By default, PHOENIX_COMPOSITE_LIMIT is set to PHOENIX_LIMIT (See Actors).

composite template class

template <typename EvalPolicy, typename EvalTuple>
struct composite : EvalTuple
{
    typedef EvalTuple base_type;
    typedef EvalPolicy eval_policy_type;

    template <typename Env>
    struct result
    {
        typedef implementation-defined type;
    };

    composite();
    composite(base_type const& actors);

    template <typename U0>
    composite(U0 const& _0);

    template <typename U0, typename U1>
    composite(U0 const& _0, U1 const& _1);

    // more constructors

    template <typename Env>
    typename result<Env>::type
    eval(Env const& env) const;
};

EvalTuple

EvalTuple, holds all the Eval instances. The composite template class inherits from it. In addition to a default constructor and a constructor from an EvalTuple object, there are templated (pass through) constructors for 1 to N arguments (again, where N == PHOENIX_COMPOSITE_LIMIT). These constructors simply forward the arguments to the EvalTuple base class.

EvalPolicy

The composite's eval member function calls its EvalPolicy's eval member function (a static member function) passing in the environment and each of its actors, in parallel. The following diagram illustrates what's happening:

funnel_out

Table 1.13. EvalPolicy Requirements

Expression

Result/Semantics

x.eval<RT>(env, eval0, eval1, ..., evalN)

Evaluate the composite

T::result<Env, Eval0, Eval1, Eval2, ..., EvalN>::type

The return type of eval


The EvalPolicy is expected to have a nested template class result which has a typedef type that reflects the return type of its member function eval. Here's a typical example of the composite's eval member function for a 2-actor composite:

template <typename Env>
typename result<Env>::type
eval(Env const& env) const
{
    typedef typename result<Env>::type return_type;
    return EvalPolicy::template
        eval<return_type>(
            env
          , get<0>(*this)   // gets the 0th element from EvalTuple
          , get<1>(*this)); // gets the 1st element from EvalTuple
}

Composites are never instantiated directly. Front end expression templates are used to generate the composites. Using expression templates, we implement a DSEL (Domain Specific Embedded Language) that mimics native C++. You've seen this DSEL in action in the preceding sections. It is most evident in the Statement section.

There are some facilities in the library to make composition of composites easier. We have a set of overloaded compose functions and an as_composite metafunction. Together, these helpers make composing a breeze. We'll provide an example of a composite later to see why.

compose<EvalPolicy>(arg0, arg1, arg2, ..., argN);

Given an EvalPolicy and some arguments arg0...argN, returns a proper composite. The arguments may or may not be phoenix actors (primitives of composites). If not, the arguments are converted to actors appropriately. For example:

compose<X>(3)

converts the argument 3 to an actor<value<int> >(3).

as_composite<EvalPolicy, Arg0, Arg1, Arg2, ..., ArgN>::type

This is the metafunction counterpart of compose. Given an EvalPolicy and some argument types Arg0...ArgN, returns a proper composite type. For example:

as_composite<X, int>::type

is the composite type of the compose<X>(3) expression above.

Now, let's examine an example. Again, this is not a toy example. This is actually part of the library. Remember the while_ lazy statement? Putting together everything we've learned so far, we will present it here in its entirety (verbatim):

struct while_eval
{
    template <typename Env, typename Cond, typename Do>
    struct result
    {
        typedef void type;
    };

    template <typename RT, typename Env, typename Cond, typename Do>
    static void
    eval(Env const& env, Cond& cond, Do& do_)
    {
        while (cond.eval(env))
            do_.eval(env);
    }
};

template <typename Cond>
struct while_gen
{
    while_gen(Cond const& cond)
        : cond(cond) {}

    template <typename Do>
    actor<typename as_composite<while_eval, Cond, Do>::type>
    operator[](Do const& do_) const
    {
        return compose<while_eval>(cond, do_);
    }

    Cond cond;
};

template <typename Cond>
while_gen<Cond>
while_(Cond const& cond)
{
    return while_gen<Cond>(cond);
}

while_eval is an example of an EvalPolicy. while_gen and while_ are the expression template front ends. Let's break this apart to understand what's happening. Let's start at the bottom. It's easier that way.

When you write:

while_(cond)

we generate an instance of while_gen<Cond>, where Cond is the type of cond. cond can be an arbitrarily complex actor expression. The while_gen template class has an operator[] accepting another expression. If we write:

while_(cond)
[
    do_
]

it will generate a proper composite with the type:

as_composite<while_eval, Cond, Do>::type

where Cond is the type of cond and Do is the type of do_. Notice how we are using phoenix's composition (compose and as_composite) mechanisms here

template <typename Do>
actor<typename as_composite<while_eval, Cond, Do>::type>
operator[](Do const& do_) const
{
    return compose<while_eval>(cond, do_);
}

Finally, the while_eval does its thing:

while (cond.eval(env))
    do_.eval(env);

cond and do_, at this point, are instances of Eval. cond and do_ are the Eval elements held by the composite's EvalTuple. env is the Environment.

We've shown how it is very easy to extend phoenix by writing new primitives and composites. The modular design of Phoenix makes it extremely extensible. We have seen that layer upon layer, the whole library is built on a solid foundation. There are only a few simple well designed concepts that are laid out like bricks. Overall, the library is designed to be extended. Everything above the core layer can in fact be considered just as extensions to the library. This modular design was inherited from the Spirit inline parser library.

Extension is non-intrusive. And, whenever a component or module is extended, the new extension automatically becomes a first class citizen and is automatically recognized by all modules and components in the library.


PrevUpHomeNext