Boost logo

Boost :

From: Maurizio Vitale (mav_at_[hidden])
Date: 2007-05-08 09:52:40

Eric Niebler <eric_at_[hidden]> writes:

> Maurizio Vitale wrote:
>> Eric Niebler <eric_at_[hidden]> 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

    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,
    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;

    struct NAME ## _c {}; \
    proto::terminal< NAME ## _c >::type NAME = {{}}

    template<> struct quantization_spillover<NAME ## _c> { \
      typedef BOOST_TYPEOF (EXPRESSION) type; \


    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
        - 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?

Boost list run by bdawes at, gregod at, cpdaniel at, john at