![]() |
Home | Libraries | People | FAQ | More |
If we limited ourselves to nothing but terminals and operator overloads,
our embedded domain-specific languages wouldn't be very expressive. Imagine
that we wanted to extend our calculator EDSL with a full suite of math
functions like sin()
and pow()
that we could invoke lazily as follows.
// A calculator expression that takes one argument // and takes the sine of it. sin(_1);
We would like the above to create an expression template representing a
function invocation. When that expression is evaluated, it should cause
the function to be invoked. (At least, that's the meaning of function invocation
we'd like the calculator EDSL to have.) You can define sin
quite simply as follows.
// "sin" is a Proto terminal containing a function pointer proto::terminal< double(*)(double) >::type const sin = {&std::sin};
In the above, we define sin
as a Proto terminal containing a pointer to the std::sin()
function. Now we can use sin
as a lazy function. The default_context
that we saw in the Introduction
knows how to evaluate lazy functions. Consider the following:
double pi = 3.1415926535; proto::default_context ctx; // Create a lazy "sin" invocation and immediately evaluate it std::cout << proto::eval( sin(pi/2), ctx ) << std::endl;
The above code prints out:
1
I'm no expert at trigonometry, but that looks right to me.
We can write sin(pi/2)
because the sin
object, which is a Proto terminal, has an overloaded operator()()
that builds a node representing a function
call invocation. The actual type of sin(pi/2)
is actually
something like this:
// The type of the expression sin(pi/2): proto::function< proto::terminal< double(*)(double) >::type const & proto::result_of::as_child< double const >::type >::type
This type further expands to an unsightly node type with a tag
type of proto::tag::function
and two children: the first
representing the function to be invoked, and the second representing the
argument to the function. (Node tag types describe the operation that created
the node. The difference between a
+ b
and a -
b
is that the former has tag
type proto::tag::plus
and the latter has tag type proto::tag::minus
. Tag types are pure compile-time
information.)
![]() |
Note |
---|---|
In the type computation above, |
It is important to note that there is nothing special about terminals that contain function pointers. Any Proto expression has an overloaded function call operator. Consider:
// This compiles! proto::lit(1)(2)(3,4)(5,6,7,8);
That may look strange at first. It creates an integer terminal with proto::lit()
, and then invokes it like
a function again and again. What does it mean? Who knows?! You get to decide
when you define an evaluation context or a transform. But more on that
later.
Now, what if we wanted to add a pow()
function to our calculator EDSL that
users could invoke as follows?
// A calculator expression that takes one argument // and raises it to the 2nd power pow< 2 >(_1);
The simple technique described above of making pow
a terminal containing a function pointer doesn't work here. If pow
is an object, then the expression
pow<
2 >(_1)
is
not valid C++. (Well, technically it is; it means, pow
less than 2, greater than (_1)
,
which is nothing at all like what we want.) pow
should be a real function template. But it must be an unusual function:
one that returns an expression template.
With sin
, we relied on
Proto to provide an overloaded operator()()
to build an expression node with tag
type proto::tag::function
for us. Now we'll need to do
so ourselves. As before, the node will have two children: the function
to invoke and the function's argument.
With sin
, the function
to invoke was a raw function pointer wrapped in a Proto terminal. In the
case of pow
, we want it
to be a terminal containing TR1-style function object. This will allow
us to parameterize the function on the exponent. Below is the implementation
of a simple TR1-style wrapper for the std::pow
function:
// Define a pow_fun function object template< int Exp > struct pow_fun { typedef double result_type; double operator()(double d) const { return std::pow(d, Exp); } };
Following the sin
example,
we want pow<
1 >(
pi/2 )
to have
a type like this:
// The type of the expression pow<1>(pi/2): proto::function< proto::terminal< pow_fun<1> >::type proto::result_of::as_child< double const >::type >::type
We could write a pow()
function using code like this, but it's verbose and error prone; it's too
easy to introduce subtle bugs by forgetting to call proto::as_child()
where necessary, resulting in code that seems to work but sometimes doesn't.
Proto provides a better way to construct expression nodes: proto::make_expr()
.
make_expr()
Proto provides a helper for building expression templates called proto::make_expr()
. We can concisely define
the pow()
function with it as below.
// Define a lazy pow() function for the calculator EDSL. // Can be used as: pow< 2 >(_1) template< int Exp, typename Arg > typename proto::result_of::make_expr< proto::tag::function // Tag type , pow_fun< Exp > // First child (by value) , Arg const & // Second child (by reference) >::type const pow(Arg const &arg) { return proto::make_expr<proto::tag::function>( pow_fun<Exp>() // First child (by value) , boost::ref(arg) // Second child (by reference) ); }
There are some things to notice about the above code. We use proto::result_of::make_expr<>
to calculate the return type. The first template parameter is the tag type
for the expression node we're building -- in this case, proto::tag::function
.
Subsequent template parameters to proto::result_of::make_expr<>
represent child nodes. If a child
type is not already a Proto expression, it is automatically made into a
terminal with proto::as_child()
.
A type such as pow_fun<Exp>
results in terminal that is held by
value, whereas a type like Arg
const &
(note the reference) indicates that the result should be held by reference.
In the function body is the runtime invocation of proto::make_expr()
.
It closely mirrors the return type calculation. proto::make_expr()
requires you to specify the node's tag type as a template parameter. The
arguments to the function become the node's children. When a child should
be stored by value, nothing special needs to be done. When a child should
be stored by reference, you must use the boost::ref()
function to wrap the argument.
And that's it! proto::make_expr()
is the lazy person's way to make a lazy funtion.