...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 PHOENIX_ARG_LIMIT, the predefined maximum placeholder index. By default, PHOENIX_ARG_LIMIT is set to PHOENIX_LIMIT (See Actors). |
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 desireable. 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 references'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).