|
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