![]() |
Home | Libraries | People | FAQ | More |
The first step to giving your calculator expressions extra behaviors is to define a calculator domain. All expressions within the calculator domain will be imbued with calculator-ness, as we'll see.
// A type to be used as a domain tag (to be defined below) struct calculator_domain;
We use this domain type when extending the proto::expr<>
type, which we do with the proto::extends<>
class template. Here is our expression wrapper, which imbues an expression
with calculator-ness. It is described below.
// The calculator<> expression wrapper makes expressions // function objects. template< typename Expr > struct calculator : proto::extends< Expr, calculator< Expr >, calculator_domain > { typedef proto::extends< Expr, calculator< Expr >, calculator_domain > base_type; calculator( Expr const &expr = Expr() ) : base_type( expr ) {} // This is usually needed because by default, the compiler- // generated assignment operator hides extends<>::operator= BOOST_PROTO_EXTENDS_USING_ASSIGN(calculator) typedef double result_type; // Hide base_type::operator() by defining our own which // evaluates the calculator expression with a calculator context. result_type operator()( double d1 = 0.0, double d2 = 0.0 ) const { // As defined in the Hello Calculator section. calculator_context ctx; // ctx.args is a vector<double> that holds the values // with which we replace the placeholders (e.g., _1 and _2) // in the expression. ctx.args.push_back( d1 ); // _1 gets the value of d1 ctx.args.push_back( d2 ); // _2 gets the value of d2 return proto::eval(*this, ctx ); // evaluate the expression } };
We want calculator expressions to be function objects, so we have to
define an operator()
that takes and returns doubles. The calculator<>
wrapper above does that with
the help of the proto::extends<>
template. The first template to proto::extends<>
parameter is the expression type we are extending. The second is the
type of the wrapped expression. The third parameter is the domain that
this wrapper is associated with. A wrapper type like calculator<>
that inherits from proto::extends<>
behaves just like
the expression type it has extended, with any additional behaviors you
choose to give it.
![]() |
Note |
---|---|
Why not just inherit from
You might be thinking that this expression extension business is unnecessarily
complicated. After all, isn't this why C++ supports inheritance? Why
can't |
Although not strictly necessary in this case, we bring extends<>::operator=
into scope with the BOOST_PROTO_EXTENDS_USING_ASSIGN()
macro. This is really only necessary
if you want expressions like _1
= 3
to create a lazily evaluated assignment. proto::extends<>
defines the appropriate operator=
for you, but the compiler-generated
calculator<>::operator=
will hide it unless you make it available with the macro.
Note that in the implementation of calculator<>::operator()
, we evaluate the expression with the
calculator_context
we
defined earlier. As we saw before, the context is what gives the operators
their meaning. In the case of the calculator, the context is also what
defines the meaning of the placeholder terminals.
Now that we have defined the calculator<>
expression wrapper, we need to
wrap the placeholders to imbue them with calculator-ness:
calculator< proto::terminal< placeholder<0> >::type > const _1; calculator< proto::terminal< placeholder<1> >::type > const _2;
BOOST_PROTO_EXTENDS()
To use proto::extends<>
, your extension type
must derive from proto::extends<>
.
Unfortunately, that means that your extension type is no longer POD and
its instances cannot be statically initialized.
(See the Static
Initialization section in the Rationale
appendix for why this matters.) In particular, as defined above, the
global placeholder objects _1
and _2
will need to be
initialized at runtime, which could lead to subtle order of initialization
bugs.
There is another way to make an expression extension that doesn't sacrifice
POD-ness : the
macro. You can use it much like you use BOOST_PROTO_EXTENDS
()proto::extends<>
.
We can use
to keep BOOST_PROTO_EXTENDS
()calculator<>
a POD and our placeholders statically initialized.
// The calculator<> expression wrapper makes expressions // function objects. template< typename Expr > struct calculator { // Use BOOST_PROTO_EXTENDS() instead of proto::extends<> to // make this type a Proto expression extension. BOOST_PROTO_EXTENDS(Expr, calculator<Expr>, calculator_domain) typedef double result_type; result_type operator()( double d1 = 0.0, double d2 = 0.0 ) const { /* ... as before ... */ } };
With the new calculator<>
type, we can redefine our placeholders
to be statically initialized:
calculator< proto::terminal< placeholder<0> >::type > const _1 = {{{}}}; calculator< proto::terminal< placeholder<1> >::type > const _2 = {{{}}};
We need to make one additional small change to accommodate the POD-ness of our expression extension, which we'll describe below in the section on expression generators.
What does
do? It defines a data member of the expression type being extended; some
nested typedefs that Proto requires; BOOST_PROTO_EXTENDS
()operator=
, operator[]
and operator()
overloads for building expression templates;
and a nested result<>
template for calculating the return type of operator()
. In this case, however, the operator()
overloads and the result<>
template are not needed because
we are defining our own operator()
in the calculator<>
type. Proto provides additional
macros for finer control over which member functions are defined. We
could improve our calculator<>
type as follows:
// The calculator<> expression wrapper makes expressions // function objects. template< typename Expr > struct calculator { // Use BOOST_PROTO_BASIC_EXTENDS() instead of proto::extends<> to // make this type a Proto expression extension: BOOST_PROTO_BASIC_EXTENDS(Expr, calculator<Expr>, calculator_domain) // Define operator[] to build expression templates: BOOST_PROTO_EXTENDS_SUBSCRIPT() // Define operator= to build expression templates: BOOST_PROTO_EXTENDS_ASSIGN() typedef double result_type; result_type operator()( double d1 = 0.0, double d2 = 0.0 ) const { /* ... as before ... */ } };
Notice that we are now using
instead of BOOST_PROTO_BASIC_EXTENDS
()
.
This just adds the data member and the nested typedefs but not any of
the overloaded operators. Those are added separately with BOOST_PROTO_EXTENDS
()
and BOOST_PROTO_EXTENDS_ASSIGN
()
.
We are leaving out the function call operator and the nested BOOST_PROTO_EXTENDS_SUBSCRIPT
()result<>
template that could have been defined with Proto's
macro.
BOOST_PROTO_EXTENDS_FUNCTION
()
In summary, here are the macros you can use to define expression extensions, and a brief description of each.
Table 1.2. Expression Extension Macros
Macro |
Purpose |
---|---|
|
Defines a data member of type |
Defines |
|
Defines |
|
Defines |
|
|
Equivalent to:
|
![]() |
Warning |
---|---|
Argument-Dependent Lookup and
Proto's operator overloads are defined in the
template<class T> struct my_complex { BOOST_PROTO_EXTENDS( typename proto::terminal<std::complex<T> >::type , my_complex<T> , proto::default_domain ) }; int main() { my_complex<int> c0, c1; c0 + c1; // ERROR: operator+ not found }
The problem has to do with how argument-dependent lookup works. The
type
So what can we do? By adding an extra dummy template parameter that
defaults to a type in the
template<class T, class Dummy = proto::is_proto_expr> struct my_complex { BOOST_PROTO_EXTENDS( typename proto::terminal<std::complex<T> >::type , my_complex<T> , proto::default_domain ) }; int main() { my_complex<int> c0, c1; c0 + c1; // OK, operator+ found now! }
The type |