...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Actors are composed to create more complex actors in a tree-like hierarchy. The primitives are atomic entities that are like the leaves in the tree. Phoenix is extensible. New primitives can be added anytime. Right out of the box, there are only a few primitives. This section shall deal with these preset primitives.
#include <boost/spirit/home/phoenix/core/argument.hpp>
We use an instance of:
actor<argument<N> >
to represent the Nth function argument. The argument placeholder acts as an imaginary data-bin where a function argument will be placed.
There are a few predefined instances of actor<argument<N>
>
named arg1
..argN
, and its BLL
counterpart _1
.._N
. (where N is a predefined maximum).
Here are some sample preset definitions of arg1
..argN
actor<argument<0> > const arg1 = argument<0>(); actor<argument<1> > const arg2 = argument<1>(); actor<argument<2> > const arg3 = argument<2>();
and its BLL
_1
.._N
style counterparts:
actor<argument<0> > const _1 = argument<0>(); actor<argument<1> > const _2 = argument<1>(); actor<argument<2> > const _3 = argument<2>();
Note | |
---|---|
You can set |
When appropriate, you can define your own argument<N>
names. For example:
actor<argument<0> > x; // note zero based index
x
may now be used as a parameter
to a lazy function:
add(x, 6)
which is equivalent to:
add(arg1, 6)
An argument, when evaluated, selects the Nth argument from the those passed in by the client.
For example:
char c = 'A'; int i = 123; const char* s = "Hello World"; cout << arg1(c) << endl; // Get the 1st argument: c cout << arg1(i, s) << endl; // Get the 1st argument: i cout << arg2(i, s) << endl; // Get the 2nd argument: s
will print out:
A 123 Hello World
In C and C++, a function can have extra arguments that are not at all used by the function body itself. These extra arguments are simply ignored.
Phoenix also allows extra arguments to be passed. For example, recall our
original add
function:
add(arg1, arg2)
We know now that partially applying this function results to a function that
expects 2 arguments. However, the library is a bit more lenient and allows
the caller to supply more arguments than is actually required. Thus, add
actually allows 2 or more
arguments. For instance, with:
add(arg1, arg2)(x, y, z)
the third argument z
is ignored.
Taking this further, in-between arguments are also ignored. Example:
add(arg1, arg5)(a, b, c, d, e)
Here, arguments b, c, and d are ignored. The function add
takes in the first argument (arg1
)
and the fifth argument (arg5
).
Note | |
---|---|
There are a few reasons why enforcing strict arity is not desirable. A case in point is the callback function. Typical callback functions provide more information than is actually needed. Lambda functions are often used as callbacks. |
#include <boost/spirit/home/phoenix/core/value.hpp>
Whenever we see a constant in a partially applied function, an
actor<value<T> >
(where T is the type of the constant) is automatically created for us. For instance:
add(arg1, 6)
Passing a second argument, 6
,
an actor<value<int> >
is implicitly created behind the scenes. This is also equivalent to:
add(arg1, val(6))
val(x)
generates
an actor<value<T> >
where T
is the type of x
. In most
cases, there's no need to explicitly use val
,
but, as we'll see later on, there are situations where this is unavoidable.
Like arguments, values are also actors. As such, values can be evaluated. Invoking a value gives the value's identity. Example:
cout << val(3)() << val("Hello World")();
prints out "3 Hello World".
#include <boost/spirit/home/phoenix/core/reference.hpp>
Values are immutable constants. Attempting to modify a value will result
in a compile time error. When we want the function to modify the parameter,
we use a reference instead. For instance, imagine a lazy function add_assign
:
void add_assign(T& x, T y) { x += y; } // pseudo code
Here, we want the first function argument, x, to be mutable. Obviously, we cannot write:
add_assign(1, 2) // error first argument is immutable
In C++, we can pass in a reference to a variable as the first argument in our example above. Yet, by default, the library forces arguments passed to partially applied functions functions to be immutable values (see Values). To achieve our intent, we use:
actor<reference<T> >
This is similar to actor<value<T>
>
above but instead holds a reference
to a variable.
We normally don't instantiate actor<reference<T>
>
objects directly. Instead we
use ref
. For example (where
i
is an int
variable):
add_assign(ref(i), 2)
References are actors. Hence, references can be evaluated. Such invocation gives the reference's identity. Example:
int i = 3; char const* s = "Hello World"; cout << ref(i)() << ref(s)();
prints out "3 Hello World"
#include <boost/spirit/home/phoenix/core/reference.hpp>
Another free function cref(cv)
may also be used. cref(cv)
creates
an actor<reference<T const&> >
object. This is similar to actor<value<T>
>
but when the data to be passed
as argument to a function is heavy and expensive to copy by value, the cref(cv)
offers a lighter alternative.
#include <boost/spirit/home/phoenix/core/nothing.hpp>
Finally, the actor<null_actor>
does nothing; (a "bum", if you will :-). There's a sole actor<null_actor>
instance named "nothing". This actor is actually useful in situations
where we don't want to do anything. (See for_
Statement for example).