
Eric Niebler <eric@boost-consulting.com> writes:
Maurizio Vitale wrote:
Eric Niebler <eric@boost-consulting.com> writes:
Nodes in proto's expression tree are held by reference, so that building an expression tree requires no copying. But some nodes are temporaries. deep_copy() forces all nodes to be held by value, which is necessary if you're storing them in a local variable like this.
Actually, I'm trying to do the opposite: I have expressions used to represent the way to do quantization and manage overflow which have noting to refer to when they're created, so they're made of operators and terminals with no data in. The type is all that is needed for evaluating them, and the evaluation context will know where to grab the data from. Although evaluation is driven by the type, it is a run-time evaluation.
I hope to be able to use evaluation contexts for this, if it doesn't work either transforms or an homebrewed solutions (but still using proto for describing the expression).
proto::eval() takes an expression and a context. So you need an expression, not just the expression's type. You can write a transform that works with types, but if you want to manipulate runtime data (and you say you do), the transforms will also need an expression object. (Recall that the transforms take an expression, in addition to the state and a visitor.)
I was sure evaluation contexts wouldn't do. Still it is a pity, I would have loved the default operators, rather than rolling my own suite. But on transforms you caught me, I did indeed forgot the first argument to call() :-(. I think I'll take a good look at the implementation and see if something springs to mind. Here's what I need for quantization handling (and similar things for overflow handling): enum quantization_mode_enum {round_to_positive_infinity, round_to_zero, round_to_negative_infinity, round_to_infinity, round_to_nearest, round_convergent, truncate_value, truncate_magnitude}; typedef mpl::integral_c<quantization_mode_enum, round_to_positive_infinity> round_to_positive_infinity_c; typedef mpl::integral_c<quantization_mode_enum, round_to_zero> round_to_zero_c; typedef mpl::integral_c<quantization_mode_enum, round_to_negative_infinity> round_to_negative_infinity_c; typedef mpl::integral_c<quantization_mode_enum, round_to_infinity> round_to_infinity_c; typedef mpl::integral_c<quantization_mode_enum, round_to_nearest> round_to_nearest_c; typedef mpl::integral_c<quantization_mode_enum, round_convergent> round_convergent_c; typedef mpl::integral_c<quantization_mode_enum, truncate_value> truncate_value_c; typedef mpl::integral_c<quantization_mode_enum, truncate_magnitude> truncate_magnitude_c; template<typename T> struct quantization_spillover; #define DECLARE_QUANTIZATION_TERMINAL(NAME) \ struct NAME ## _c {}; \ proto::terminal< NAME ## _c >::type NAME = {{}} #define DECLARE_QUANTIZATION_MODE(NAME, EXPRESSION) \ template<> struct quantization_spillover<NAME ## _c> { \ typedef BOOST_TYPEOF (EXPRESSION) type; \ } DECLARE_QUANTIZATION_TERMINAL (deleted_msb); DECLARE_QUANTIZATION_TERMINAL (deleted_lsbs); DECLARE_QUANTIZATION_TERMINAL (preserved_msb); DECLARE_QUANTIZATION_TERMINAL (preserved_lsb); DECLARE_QUANTIZATION_TERMINAL (sign); DECLARE_QUANTIZATION_MODE (round_to_positive_infinity, deleted_msb); DECLARE_QUANTIZATION_MODE (round_to_infinity, deleted_msb); DECLARE_QUANTIZATION_MODE (round_convergent, deleted_msb & (preserved_lsb | !deleted_lsbs)); DECLARE_QUANTIZATION_MODE (round_to_zero, deleted_msb & (sign | !deleted_lsbs)); DECLARE_QUANTIZATION_MODE (round_to_negative_infinity, deleted_msb & !deleted_lsbs); DECLARE_QUANTIZATION_MODE (truncate_value, 0); DECLARE_QUANTIZATION_MODE (truncate_magnitude, sign & (deleted_msb | !deleted_lsbs)); Then with quantization_spillover<MODE>::type I get the (type of the) expression that needs to be evaluated. The value of terminals will be themselves function of the source and target type and value. For instance, deleted_msb will be the bit at position I in the source, where I is target'scale - source'scale. I will be in most cases a compile-time constant, but I'll have to support the case where it si a run-time value as well. Evaluation iself shouldn't require to build an object of that type. This is what I'd do by hand: template<typename Expr> struct eval {}; template<> struct eval< boost::proto::expression<boost::proto::tags::bitwise_and ....> { int call () { typedef ...some boost::proto type selector... left; typedef ...some boost::proto type selector... right; return left::call () & right::call (); } It would be wonderful if boost could help me here, leaving to me only the task to define the semantics of terminals (and of those operators (like << and >>) for which the default semantics wont do it for me).
I considered that, but decided against it. It would take an extra compile-time conditional to decide whether to store by value or reference (that is, at least one extra template instantiation per terminal type). And lots of stuff would still be stored by reference, so people will still need to use deep_copy(). I don't see an obvious win to storing some terminals by value and others by reference. I'm happy to be proven wrong, however.
Mhhh. I'll be back on this when I start looking seriously at the assembly code produced for my expressions. For now it looks good (seems like in simple cases the compiler manages to optimize away lot of stuff), although I still have mov instructions I'd rather not have. If proven useful a possibility would be a CPP #define that says one of three: - all by ref - all by value - terminal decide. Maybe "terminal decide" can be made so that you do not get a compile type conditional: what happens if terminal defines explicitely the way it want to be stored, so that terminal< >::stored_as directly give you the value/ref behaviour?