![]() |
Home | Libraries | People | FAQ | More |
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.
The main concept is the Actor
.
Actors are function objects (that can accept 0 to N arguments (where N is
a predefined maximum).
![]() |
Note |
---|---|
You can set |
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 |
---|---|
|
The actor's Eval type |
|
Default Constructor |
|
Constructor from Eval |
|
Pass through constructors |
|
Function call operators |
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 |
---|---|
|
Evaluates the function (see Environment below) |
|
The return type of eval (see Environment below) |
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 |
---|---|
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. |
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 |
---|---|
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 |
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 |
---|---|
|
The arguments in a tie (a tuple of references) |
|
The arguments' types in an MPL sequence |
|
The tie (tuple of references) type |
Schematically:
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
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)); }
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 |
---|---|
In a sense, the original definition of "composite", more or less,
will do just fine because |
![]() |
Note |
---|---|
You can set |
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
, 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.
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:
Table 1.13. EvalPolicy Requirements
Expression |
Result/Semantics |
---|---|
|
Evaluate the composite |
|
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.