[proto] Matching grammars in the presence of adapted types
Hi, First off, this whole thing uses Boost 1.42. I'm trying to define arithmetic types where some expression patterns will be matched and replaced by something else (e.g. a & ~b, the andnot pattern). The whole thing is a proof-of-concept and teach-myself-proto thing that should eventually do SIMD operations. Right now, though, it simply wraps integers. First I tried defining my type as a proto type this way: ----------------- template <typename Expr> class simd_expr : public proto::extends<Expr, simd_expr<Expr>, simd_domain> { /* as in the tutorial */ }; class smartint : public simd_expr<proto::terminal<smartint>::type> { int value; // ... }; inline smartint ilit(int i) { return smartint(i); } ----------------- However, this didn't work, because at the instantiation point of simd_expr<proto::terminal<smartint>::type>, smartint is incomplete, and something in proto doesn't like that. So I used the only thing in the docs that looked like my use case, and did the non-intrusive adapting thing demonstrated on the matrix and vector classes. ----------------- class smartint { ... }; template <typename T> struct is_terminal : mpl::false_ {}; template <> struct is_terminal<smartint> : mpl::true_ {}; BOOST_PROTO_DEFINE_OPERATORS(is_terminal, simd_domain) ----------------- This works nicely for normal evaluations. I have a simd_context, which knows how to evaluate smartint, and forwards everything else to default_context: ----------------- struct simd_context : proto::callable_context<simd_context const, proto::default_context> { typedef int result_type; int operator ()(proto::tag::terminal, smartint const &si) const; }; inline int simd_context::operator ()( proto::tag::terminal, smartint const &si) const { return si.get(); } ----------------- Now I can do fun stuff like this: ----------------- int i = ilit(7) + 3; // simd_expr has 'operator int' ----------------- Now I want to match specific expression patterns and do something special for them. For example, in this expression: ----------------- i = 1 * (0xF0 & ~ilit(0x70)); ----------------- I want to match the 'a & ~b' pattern and treat it specially. So first, I define this pattern as a grammar: ----------------- struct andnot_pattern : proto::bitwise_and<proto::_, proto::complement<proto::_>> {}; ----------------- And then I add another function to my context that applies only to expressions matching this pattern: ----------------- template <typename Tag, typename Expr> typename boost::enable_if< proto::matches<Expr, andnot_pattern>, int>::type operator ()(Tag, Expr const &e) const { std::cerr << "Found andnot pattern\n"; return 0xFF; } ----------------- However, when I try to compile this, VC++ 2010 complains: boost_1_42_0\boost\proto\matches.hpp(502): error C2039: 'proto_base_expr' : is not a member of 'smart::smartint' Pointing here: ----------------- template<typename Expr, typename Grammar> struct matches : detail::matches_< typename Expr::proto_base_expr , typename Grammar::proto_base_expr > {}; ----------------- So apparently, while callable_context is trying to find an appropriate overload for some stuff, it tries out my template operator, passing a smartint as Expr, and matches<> can't handle adapted types. Because the error is within matches<>, it's not SFINAEable and the compiler errors out instead of eliminating the function. What can I do? Can I teach matches<> to handle adapted types? Is there a "right" way to implement smartint so that it needn't be an adapted type, which would work around the whole issue? Is this a bug in Proto that is fixed in trunk, or can be fixed? Sebastian
Sebastian Redl wrote:
Hi,
First off, this whole thing uses Boost 1.42.
I'm trying to define arithmetic types where some expression patterns will be matched and replaced by something else (e.g. a & ~b, the andnot pattern). The whole thing is a proof-of-concept and teach-myself-proto thing that should eventually do SIMD operations. Right now, though, it simply wraps integers.
Very laudable goal ;) I think i can give you some hints :p
What can I do? Can I teach matches<> to handle adapted types? Is there a "right" way to implement smartint so that it needn't be an adapted type, which would work around the whole issue? Is this a bug in Proto that is fixed in trunk, or can be fixed?
You need a grammar transform, not a context. struct optimize_andnot : or_< where< proto::bitwise_and<proto::_, proto::complement<proto::_>> , your_optimized_andnot_expression<type>( left_(_),child0_(right_(_)) > , otherwise<_>
{};
(more or less typos) Basically if yopu match a & ~b, you extarct left_ of the expression (ie a) then the child of the right part (ie b) and you construct a new node from that. Applyting this transform is done like: optimize_andnot optimizer; auto optimized = optimizer(your_expression); should return you the modified AST where a & ~b have been replaced everywhere by andnot(a,b). That's how we handle all the fused operations in NT² including vec_madd on altivec and such.
On 6/4/2010 11:05 AM, Sebastian Redl wrote:
Hi,
First off, this whole thing uses Boost 1.42.
I'm trying to define arithmetic types where some expression patterns will be matched and replaced by something else (e.g. a & ~b, the andnot pattern). The whole thing is a proof-of-concept and teach-myself-proto thing that should eventually do SIMD operations. Right now, though, it simply wraps integers.
First I tried defining my type as a proto type this way:
-----------------
template <typename Expr> class simd_expr : public proto::extends<Expr, simd_expr<Expr>, simd_domain> { /* as in the tutorial */ };
class smartint : public simd_expr<proto::terminal<smartint>::type> { int value;
// ... };
inline smartint ilit(int i) { return smartint(i); }
-----------------
However, this didn't work, because at the instantiation point of simd_expr<proto::terminal<smartint>::type>, smartint is incomplete, and something in proto doesn't like that.
It's equivalent to defining a type that has itself as a member. So yeah, that's not going to work. What are you trying to do?
So I used the only thing in the docs that looked like my use case, and did the non-intrusive adapting thing demonstrated on the matrix and vector classes.
-----------------
class smartint { ... }; template <typename T> struct is_terminal : mpl::false_ {}; template <> struct is_terminal<smartint> : mpl::true_ {}; BOOST_PROTO_DEFINE_OPERATORS(is_terminal, simd_domain)
-----------------
This works nicely for normal evaluations. I have a simd_context, which knows how to evaluate smartint, and forwards everything else to default_context:
-----------------
struct simd_context : proto::callable_context<simd_context const, proto::default_context> { typedef int result_type; int operator ()(proto::tag::terminal, smartint const &si) const; };
inline int simd_context::operator ()( proto::tag::terminal, smartint const &si) const { return si.get(); }
-----------------
Now I can do fun stuff like this:
-----------------
int i = ilit(7) + 3; // simd_expr has 'operator int'
-----------------
Now I want to match specific expression patterns and do something special for them. For example, in this expression:
-----------------
i = 1 * (0xF0 & ~ilit(0x70));
-----------------
I want to match the 'a & ~b' pattern and treat it specially. So first, I define this pattern as a grammar:
-----------------
struct andnot_pattern : proto::bitwise_and<proto::_, proto::complement<proto::_>> {};
-----------------
And then I add another function to my context that applies only to expressions matching this pattern:
-----------------
template <typename Tag, typename Expr> typename boost::enable_if< proto::matches<Expr, andnot_pattern>, int>::type operator ()(Tag, Expr const &e) const { std::cerr << "Found andnot pattern\n"; return 0xFF; }
-----------------
However, when I try to compile this, VC++ 2010 complains: boost_1_42_0\boost\proto\matches.hpp(502): error C2039: 'proto_base_expr' : is not a member of 'smart::smartint'
Pointing here:
-----------------
template<typename Expr, typename Grammar> struct matches : detail::matches_< typename Expr::proto_base_expr , typename Grammar::proto_base_expr > {};
-----------------
Seems like a bug in Proto. Can you file a bug and attach a complete, compilable example that demonstrates the problem?
So apparently, while callable_context is trying to find an appropriate overload for some stuff, it tries out my template operator, passing a smartint as Expr, and matches<> can't handle adapted types. Because the error is within matches<>, it's not SFINAEable and the compiler errors out instead of eliminating the function.
What can I do? Can I teach matches<> to handle adapted types? Is there a "right" way to implement smartint so that it needn't be an adapted type, which would work around the whole issue? Is this a bug in Proto that is fixed in trunk, or can be fixed?
Well, using proto::extends is the way to go. I can help you do that, once I figure out what you're really mean by: class smartint : public simd_expr<proto::terminal<smartint>::type> Usually, you can handle this sort of thing by making a separate smartint_impl class that has the data, and defining smartint as: class smartint : public simd_expr<proto::terminal<smartint_impl>::type> Does that help? -- Eric Niebler BoostPro Computing http://www.boostpro.com
Eric Niebler wrote:
Seems like a bug in Proto. Can you file a bug and attach a complete, compilable example that demonstrates the problem?
OK, will do on Monday.
Well, using proto::extends is the way to go. I can help you do that, once I figure out what you're really mean by:
class smartint : public simd_expr<proto::terminal<smartint>::type>
Usually, you can handle this sort of thing by making a separate smartint_impl class that has the data, and defining smartint as:
class smartint : public simd_expr<proto::terminal<smartint_impl>::type>
Does that help?
That seems to be what I want to do. My goal is to have a Blitz++-style type, i.e. every expression written on a single line is first captured as an expression template, optimized by metafunctions, and evaluated as a whole. smartint is the basic arithmetic type in this scheme, so I want every expression involving a smartint to be a Proto expression (simd_expr). simd_expr then has an implicit conversion back to smartint that evaluates the expression. Sebastian
So I've figured out the problem. Basically, I was doing something like this: --------------------- struct SmartContext { template<typename Expr, typename Enabler = void> struct eval; template<typename Expr> struct eval<Expr, /* something here to catch arity == 0 */> { /* implementation for terminals */ }; template<typename Expr> struct eval<Expr, /* something here that looks at the children of Expr */> { /* one implementation for non-terminals */ }; template<typename Expr> struct eval<Expr, /* something else here that looks at the children of Expr */> { /* another implementation for non-terminals */ }; }; --------------------- Unfortunately, those specializations are all tested at the same time. Thus, even for terminal expressions, the stuff looking at the children was evaluated. However, the "child" of a terminal is the underlying value implementation, so this stuff tried to access the value implementation as if it was an expression. Thus the compilation errors. Is there a particular reason why terminals are implemented this way? Does it make Proto implementation considerably more convenient? Because if not, it would be nice to change it so that proto::child() and proto::value() are not synonyms, but instead simply don't compile for terminals and non-terminals, respectively. That would have helped a lot in detecting this error. Actually, a static_assert would suffice. Something like: --------------------- namespace proto { namespace result_of { template <typename Expr> struct value : proto::result_of::child_n<Expr, 0> { static_assert(proto::arity_of<Expr> == 0, "Accessing the value of a non-terminal."); }; }} --------------------- Sebastian
On 6/10/2010 11:57 AM, Sebastian Redl wrote:
So I've figured out the problem. Basically, I was doing something like this:
---------------------
struct SmartContext {
template<typename Expr, typename Enabler = void> struct eval;
template<typename Expr> struct eval<Expr, /* something here to catch arity == 0 */> { /* implementation for terminals */ };
template<typename Expr> struct eval<Expr, /* something here that looks at the children of Expr */> { /* one implementation for non-terminals */ };
template<typename Expr> struct eval<Expr, /* something else here that looks at the children of Expr */> { /* another implementation for non-terminals */ };
};
---------------------
Unfortunately, those specializations are all tested at the same time. Thus, even for terminal expressions, the stuff looking at the children was evaluated.
Using SFINAE in proto context types means you should probably be using transforms instead. Contexts are useful when you can dispatch on node tag type. Anything fancier and transforms are the way to go.
However, the "child" of a terminal is the underlying value implementation, so this stuff tried to access the value implementation as if it was an expression. Thus the compilation errors.
Is there a particular reason why terminals are implemented this way? Does it make Proto implementation considerably more convenient? Because if not, it would be nice to change it so that proto::child() and proto::value() are not synonyms, but instead simply don't compile for terminals and non-terminals, respectively. That would have helped a lot in detecting this error.
Actually, a static_assert would suffice. Something like: <snip>
I'll consider adding it, but I worry about breaking people who are unknowingly and incorrectly relying on this behavior. Hmm... -- Eric Niebler BoostPro Computing http://www.boostpro.com
participants (3)
-
Eric Niebler
-
Joel Falcou
-
Sebastian Redl