Boost logo

Boost :

From: Greg Chicares (chicares_at_[hidden])
Date: 2001-07-15 17:23:35


Michael Kenniston wrote:
>
> In the design of the Quantity library, a trade-off
> arose which might be of more general interest.
> It affects the interface, not just the implementation,
> so it's important to get it "right".

I like
  meter * kilogram / square( second )
much better than
  meter() * kilogram() / square( second() )
The question is whether it can be done cleanly
enough. I propose an implementation below.

> The general problem is the need for a bunch of constants
> of type T, where T is a user-defined class. In the
> specific case of the quantity library, T is
> "quantity<...>" and the constants are things like
> "foot" and "meter".
>
> There are (at least) two ways of handling this:
>
> 1 - const T mything( init_value );
> // ...
> T x = mything;
>
> 2 - inline T mything() { return mything( init_value ); }
> // ...
> T x = mything();
>
> If the constructor for T is simple enough, either of
> the two methods should allow a good compiler to optimize
> well by inlining the init_value, so efficiency does not
> appear to be a major consideration here.

It's nice that you provide performance tests, so one can
substitute demonstrations for impressions. I didn't make
time to do that.

> The first method is certainly more conventional, provides
> a cleaner, more concise syntax without the extraneous "()",
> and so is probably a little easier to understand and to use.
> It also clearly expresses the "const" nature of mything.

I'd say this makes it much easier for inexperienced programmers
to use without understanding the implementation: a Good Thing.

> However, it also introduces the problem of guaranteeing
> correct initialization of static variables before they
> are used. There are certainly documented techniques in
> the literature to guarantee initialization before use, but
> they are not without problems:
>
> 1) The efficiency requirement (access to mything must be
> as fast as access to a double) prohibits some of the usual
> techniques (e.g. a bool "initialized" flag).
>
> 2) The portability requirement (code must not rely on anything
> implementation-dependent) makes me wary of anything very
> subtle or sophisticated - even if my code is ISO-compliant,
> the compiler might not be.

OK, here's an implementation for your consideration.

-----begin-----
#include "boost/quantity/quantity.hpp"
#include <iostream>

// Tested only with gcc-2.95.2-1 with
// -Wall -ansi
// -pedantic complains about the .hpp; I didn't look into it.

// Start with your sample program:

namespace hyperspace {
using namespace boost;
using namespace boost::quantity;

// warp factor dimensions are m-2 kg+3 s-1 A

typedef boost::quantity::dimensions< -2, 3, -1, 1 > warp_factor_d;

// standard unit of measurement for warp factor is the Scotty

inline boost::quantity::quantity< warp_factor_d > scotty()
{
        using boost::quantity::meter;
        using boost::quantity::kilogram;
        using boost::quantity::second;
        using boost::quantity::ampere;

        return 1.0
                / meter() / meter()
                * kilogram() * kilogram() * kilogram()
                / second()
                * ampere();
}
} // namespace hyperspace

// So far, this is copied from your example.
// Now add a wrapper template to force initialization:

template<typename T>
class initialize_wrapper
{
  public:
    // Taking address of static member forces initialization.
    initialize_wrapper(){t = &thing_wrapped;}
    T operator()() {return thing_wrapped;}

  private:
    static const T thing_wrapped;
    // cv because I don't want to change it, but I don't want
    // the compiler to optimize away the assignment in the ctor.
    // Taking the address is a side effect that prohibits
    // eliminating it under the as-if rule, so I'm probably just
    // being superstitious--remove volatile if no compiler chokes.
    T const volatile* t;
};

namespace hyperspace
{
    typedef boost::quantity::quantity< warp_factor_d > warp_type;
    typedef initialize_wrapper<warp_type> scotty_type;
    const warp_type scotty_type::thing_wrapped =
        1.0
        / square(boost::quantity::meter())
        * cube(boost::quantity::kilogram())
        / boost::quantity::second()
        * boost::quantity::ampere();

    // We could have saved one '()' by defining
    // operator T()
    // instead of
    // T operator()()
    // but then we'd have to worry about whether the
    // conversion operator did anything surprising.

    const boost::quantity::quantity< warp_factor_d > TheScotty =
        scotty_type()();

    // Macros could be written to do the dirty work
    // if you like (I don't). Of course, you could
    // provide them as a convenience without using
    // them yourself.

} // namespace hyperspace

// Now see if it works.

int main()
{
    // Here's your example.
        boost::quantity::quantity< hyperspace::warp_factor_d > full_power =
          3.27
        * boost::quantity::mega()
        * hyperspace::scotty()
        ;
    std::cout << full_power / ( hyperspace::scotty() ) << " MSc\n";

    // Here's the alternative.

    // We start hearing "my engines canna take it, Cap'n" around
    // warp ten, so I guess warp one is a deciScotty.
        boost::quantity::quantity< hyperspace::warp_factor_d > warp_one =
          1.0
        * boost::quantity::deci()
        * hyperspace::TheScotty;
        ;
    std::cout << full_power / hyperspace::TheScotty << " MSc\n";
    std::cout << full_power / warp_one << " MSc\n";
}
------end------

> 3) I want the library users to be able to define their own
> constants, without burdening them with complex requirements
> of a fancy initialization strategy.

I bet most users won't want to do that. Those that want to
are likely to cut, paste, and edit code that's already in
the library.

> The second method also makes it very hard to come up with
> a work-around, that isn't really, really ugly, for the
> conflict of keyword "pascal" with SI unit "pascal" .

What am I missing? I should think you need some sort of
#define...#undef trick that would work as well with
either method.

> For all of these reasons, I've tentatively chosen the second
> approach (inline functions) for the library. Having that
> extra "()" all over the place isn't the prettiest syntax,
> but if you think of the inline routines as being sort of
> like accessors then it's no worse than get() and put().
> In return the code is simple, robust, and easy to understand.
>
> I'd be interested in hearing your opinions: Does this seem
> like a reasonable trade-off, or might I be overlooking
> something here?

I'd choose otherwise. But it's your choice.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk