Boost logo

Boost :

From: Jeremy Maitin-Shepard (jbms_at_[hidden])
Date: 2003-10-25 19:04:48

"Andy Little" <andy_at_[hidden]> writes:
> "Jan Langer" <jan_at_[hidden]> wrote in message
> news:bndk42$97m$
>> Andy Little wrote:
>> >>and what should be the return type of an addition of kilometers and
>> >> inches.
>> >
>> > using your scheme I have absolutely no idea... :-)
>> > Try here for an answer:
>> >
>> i looked at the implementation and found the surprising answer that you
>> just take the units of the first operand. but consider the example:
>> kilometers + meters
>> a user might think that a value_type of int is sufficient for his needs.
>> then he adds 2 kilometers and 5 meters and he gets 2 kilometers.
> if he
>> does it the other way round he'll get 2005 meters. thats quite
> error-prone.

A solution for this issue, and a number of other issues, is delayed
evaluation using expression templates, which do not actually perform any
operations until the expression is converted to the base type for the
dimensional. Assume the following definitions of dimensions and units:

namespace physics {
  struct length_dimension;
  struct mass_dimension;
  /* ... */

  // This is defined specially so that it is the "base" type for
  // length_dimension. This has no knowledge of a scalar type. This
  // would generally be defined using a macro, i. e.
  // BOOST_MAKE_DIMENSION_BASE_TYPE(length, length_dimension)
  template <class StoredType, class CalculationType>
  struct length;

  /* ... */

  namespace si {

    // Returns a delayed-evaluation expression. x is _always_ in
    // 1-unit-lengths.
    template <class T> /* expression-template type */ meters(T x);

    /* ... */

  namespace imperial {
    // Returns a delayed-evaluation expression. x is _always_ in
    // 254/10000 = 127/5000 unit-lengths.
    template <class T> /* expression-template type */ inches(T x);

    /* ... */

Then the following would be possible:

// l stores length units in an int, does conversions and calculations
// with length units stored as doubles. The second template argument
// defaults to the // value of the first template argument.
physics::length<int, double> l;

// This is an expression template that encapsulates the addition
// operation and the conversion operation.
physics::si::meters(2) + physics::si::inches(3);

// Assign something to l, conversions and calculations are done with
// everything (including the conversion factors) cast to double.
l = physics::si::inches(15) + physics::si::meters(2) +
    physics::si::inches(15) + physics::si::inches(15);

// If expression templates were not used, l would equal 2.
assert(l == meters(3));

Conversion between physics::length<float, ...> and physics::length<double>
is easily defined in terms of the conversion between float and double.

This system very conveniently allows for length<interval<double> > in
order to propagate uncertainty.

More interestingly though, it allows one to define a wrapper for int:

template <class T, int amount, int base = 10>
class shifted { T value; public: /* ... */ };

T should be a numerical type. Class shifted overloads all usual
arithmetic operators, but objects of types instantiated from shifted
convert to other types, including int, as if the object represented the
value: value * pow(base, amount)

Then, one could use:
physics::length<shifted<int, 6> >

This would give the same range of values as would using 1 kilometer as
the unit length, but IMHO, this is a much better way to achieve that.
It would greatly simplify what the units framework would need to do,
and allow easier use of the units framework by users that need to use a
"shifted" representation. Furthermore, shifted could be used outside
of the units framework.

What are your thoughts?

Jeremy Maitin-Shepard

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