
Tough questions. You've touched on a weakness of proto. I hope to make improvements in this area after BoostCon ... Maurizio Vitale wrote:
Suppose I needed a new tag for representing some high-level concept that I do not want to express in terms of C++ operators [it could be a get_bit(N) operation that has very different implementation when for my numbers the unserlyining implementation is a builtin type or a GMP big int]
Here's my code for adding a binary operator my_plus:
struct my_plus {};
template<typename Left, typename Right> proto::expr<my_plus, proto::args2<proto::ref_<Left>, proto::ref_<Right> > > const make_my_plus_expr(Left& left, Right& right) { typedef proto::expr<my_plus, proto::args2<proto::ref_<Left>, proto::ref_<Right> > > expr_type; expr_type that={left,right}; return proto::generate<typename Left::domain, expr_type>::make (that); }
Questions:
- is the above the right way, or is there some friendlier interface?
An improvement to the return type would be: proto::expr< my_plus , proto::args2< typename proto::as_arg<Left>::type , typename proto::as_arg<Right>::type >
const
proto::as_arg<> has the job of wrapping proto types in ref_<>, and non-proto types in terminal<>::type.
- if I wanted to support the case where left and/or right are not proto expressions, it would be enough to call generate<>::make on the non proto-ized one(s) to turn them into proto::terminal, right? Maybe proto could provide a mechanism for doing this, seems common enough [might be enough to raise as_expr_if2 from proto::detail].
Yes, I think you're right. Some high-level wrapper like as_expr_if2 is needed. Essentially, you'll need something like: typename my_generate< typename my_domain_of<Left>::type , typename my_domain_of<Right>::type , proto::expr< /*see above*/ >
::type const
where my_domain_of<X> returns the domain of X or proto::default_domain if X if not a proto type, and my_generate<> checks that the domains are compatible (the same, or else one is the default_domain) and that the proto::expr< /**/ > is a valid expr for that domain. This is, sadly, an exercise for the reader at this point. As is extending this to N arguments.
But clearly then people like me will ask for N up to BOOST_PROTO_MAX_ARITY. Maybe a separate CPP constant could be used to keep the number of permutations low. Or deal separately with >2 args with an mpl (or fusion) sequence. Pretty please...
Well, there's unpack_expr() in proto/make_expr.hpp which unpacks a fusion sequence into a proto expression. But it's pretty half-baked at the moment. This needs work.
[btw, if I understand the code in operators.hpp, proto doesn't explicitely check that the domains of the two operands are the same. This situation doesn't seem really supported, nor desirable, and maybe code doing it wouldn't compile anyhow. But in case it did compile and do wrong things you might consider slowing down compilation and adding a check]
Line 95 in operators.hpp has the check you're looking for.
- is it correct that all I have to do then is to either: - make my_plus disappear before evaluation (via transforms), or - provide an evaluation context with the appropriate overload for operator()(my_plus, ...). For instance the following would give to my_plus the normal meaning of '+':
template<typename Left, typename Right> double operator() (my_plus, const Left& left, const Right& right) const{ return proto::eval(left, *this) + proto::eval (right, *this); }
That's right.
- a simple example for this would be very nice for the documentation.
Agreed. -- Eric Niebler Boost Consulting www.boost-consulting.com