Link to home
Start Free TrialLog in
Avatar of jdpipe
jdpipeFlag for Australia

asked on

Debugging: compile-time units-of-measurement tracking class for C++

Hi all

I've been working on a compile-time units-of-measurement tracking class for C++ based on an article written at embedded.com. I'm having some trouble debugging so if anyone's got the answer to my errors, please let me know.

I want the program to correctly compile with valid unit operations, and to fail compile when invalid operations such as Length x = 5 * second + 3 * joule.

Here are the error messages I get at the moment (code follows):

--------------------------------8<----------------------------

john@john ~/dsg-transient/units
$ make test
g++ -fmessage-length=0 -ggdb -DCURSES_H=1 -DHAVE_GETOPT -DHAVE_STRING_H -DVERSION=\"i686-pc-cygwin-2.2\" -W -O3  -c -o u
nits.o units.cpp
In file included from units.cpp:1:
units.h:250: error: conversion from `Units<0, 1, 0, 0, 0>' to non-scalar type `Units<0, 2, 0, 0, 0>' requested
units.h:253: error: conversion from `Units<0, 2, 0, 0, 0>' to non-scalar type `Units<0, 3, 0, 0, 0>' requested
units.h:261: error: conversion from `Units<1, 0, 0, 0, 0>' to non-scalar type `Units<1, 1, -2, 0, 0>' requested
units.h:263: error: conversion from `Units<1, 1, -2, 0, 0>' to non-scalar type `Units<1, -1, -2, 0, 0>' requested
units.h:266: error: conversion from `Units<1, 1, -2, 0, 0>' to non-scalar type `Units<1, 2, -2, 0, 0>' requested
units.h:268: error: conversion from `Units<1, 2, -2, 0, 0>' to non-scalar type `Units<1, 2, -3, 0, 0>' requested
make: *** [units.o] Error 1

--------------------------------8<----------------------------

It seems it's not using the multiplication-with-units method which is defined.

Here's the test program:

--------------------------------8<----------------------------

#include "units.h"

#include <iostream>
#include <iomanip>
#include <getopt.h>
using namespace std;

int main(int argc, char *argv[]){

      try{
      
            Velocity v = 60 * kilometre / hour;
            
            Length x = 250 * kilometre;
            
            Time t = x / v;
            
            cerr << "The time taken to travel " << x << " at speed " << v << " is " << t << endl;
            
      }catch(...){
            cerr << "FATAL ERROR: unknown";
            exit(1);
      }

}

--------------------------------8<----------------------------

Here's the header file with the unit tracking class template:

--------------------------------8<----------------------------
/// UNIT CHECKING / TRACKING CLASS
/**
      Adapted from code given by Rettig at
      http://www.embedded.com/shared/printableArticle.jhtml?articleID=9900094
      
      Code (currently) downloadable from
      http://www.embedded.com/code/2001code.htm
      
      Original Author:  Christopher Rettig ( rettigcd@bigfoot.com )
*/

#ifndef UNITS_H
#define UNITS_H

#define CHECK_UNITS

#ifdef CHECK_UNITS

      // All of the reinterpret casts are work-arounds to let us make
      // val & Units(double) private without using friends
      
      /**
            Purpose:
             Template class to create a 'Units' data type.
              It checks that units are correct at *COMPILE* time.
              It hides conversion constants.
              It enforces self documenting code.

            Compilers known compliant:
                  . GNU gcc 3.3.1 (cygming special)
      */
      template<int M,int L,int T, int K, int I>
      class Units {

            public:
                  
                  /// Initialiser
                  Units(double v) : val(v){}
                  
                  /// Copy constructor
                  Units( const Units& u ) : val( u.val ){}
                  
                  ///      Assignment operator
                  const Units& operator=( const Units& u ){      val=u.val; return *this; }

                  // MULTIPLICATION AND DIVISION BY SCALARS
                  
                  Units operator*( double d ) const { return val*d; }
                  Units operator/( double d ) const { return val/d; }
                  const Units& operator*=( double d ) { val*=d; return *this; }
                  const Units& operator/=( double d ) { val/=d; return *this; }

                  // ADDITION/SUBTRACTION WITH UNITS
                  
                  Units operator+( const Units& u ) const {      return val+u.val; }
                  Units operator-( const Units& u ) const {      return val-u.val; }
                  Units& operator+=( const Units& u ) { val+=u.val; return *this; }
                  Units& operator-=( const Units& u ) { val-=u.val; return *this; }
                  Units operator-() const { return -val; }

                  // COMPARATIVE OPERATORS
                  
                  bool operator==( const Units& u ) const { return val==u.val; }
                  bool operator!=( const Units& u ) const { return val!=u.val; }
                  bool operator< ( const Units& u ) const { return val< u.val; }
                  bool operator<=( const Units& u ) const { return val<=u.val; }
                  bool operator> ( const Units& u ) const { return val> u.val; }
                  bool operator>=( const Units& u ) const { return val>=u.val; }

                  /// Cast to scalar
                  /**
                        Not defined here because only unitless can cast to double
                  */
                  operator double() const;

                  ///      Multiplication with units
                  template< int mm, int ll, int tt, int kk, int ii >
                  Units< M+mm, L+ll, T+tt, K+kk, I+ii >
                  operator*( const Units<mm,ll,tt,kk,ii>& u ) const {
                        //Units<M+m, L+l, T+t, K+k, I+i> r;
                        //*reinterpret_cast<double*>(&r) = val * *reinterpret_cast<const double*>(&u);
                        return Units<M+mm,L+ll,T+tt,K+kk,I+ii>(u.val);
                        //return r;
                  }

                  /// Division with units
                  template< int m, int l, int t, int k, int i >
                  Units< M-m, L-l, T-t, K-k, I-i>
                  operator/( const Units<m,l,t,k,i>& u ) const {
                        Units<M-m, L-l, T-t, K-k, I-i> r;
                        *reinterpret_cast<double*>(&r) = val / *reinterpret_cast<const double*>(&u);
                        return r;
                  }

            private:

                  double val;

                  // used by */+- to make returning values easy
                  //Units( double v ):setval(v){}

      };


      /// Casting to double
      /**
            Only defined for unitless types
      */
      inline Units<0,0,0,0,0>::operator double() const {
            return val;
      }
      
      /// Scalar multiplication
      template<int M, int L, int T, int K, int I>
      inline Units< M, L, T, K, I >
      operator*( double d, const Units< M, L, T, K, I > &u) {
            return u*d;
      }

      /// Scalar division
      template<int M, int L, int T, int K, int I >
      inline Units< -M, -L, -T, -K, -I >
      operator/( double d, const Units< M, L, T, K, I >& u) {
            Units< -M, -L, -T, -K, -I > r;
            *reinterpret_cast<double*>(&r) = d / *reinterpret_cast<const double*>(&u);
            return r;
      }

      //--------------------------------
      // BASE MEASURES
      
      typedef Units< 1,  0,  0,  0,  0 >      Mass;
      typedef Units< 0,  1,  0,  0,  0 >      Length;
      typedef Units< 0,  0,  1,  0,  0 >      Time;
      typedef Units< 0,  0,  0,  1,  0 >      Temperature;
      typedef Units< 0,  0,  0,  0,  1 >      Current;

      //--------------------------------
      // DERIVED MEASURES
      
      typedef Units< 0,  2,  0,  0,  0 >      Area;
      typedef Units< 0,  3,  0,  0,  0 >      Volume;
      typedef Units< 1,  1, -2,  0,  0 >      Force;
      typedef Units< 1, -1, -2,  0,  0 >      Pressure;
      typedef Units< 0,  1, -1,  0,  0 >      Velocity;
      typedef Units< 0,  1, -2,  0,  0 >  Acceleration;

      typedef Units< 1,  2, -2,  0,  0 >      Torque;
      typedef Units< 1,  2, -2,  0,  0 >      Energy;
      typedef Units< 1,  2, -3,  0,  0 >      Power;

      typedef Units< 0,  2, -2,  0,  0 >      SpecificEnergy;
      typedef Units< 1,  2, -2, -1,  0 >  Entropy;
      typedef Units< 0,  2, -2, -1,  0 >  SpecificEntropy;
      
      typedef Units< 1,  1, -3,  0, -1 >  Conductivity;
      
      typedef Units< 0,  3, -1,  0,  0 >      Flowrate;

      // Electrical
      typedef Units< 0,  0,  1,  0,  1 >      Charge;
      typedef Units< 1,  2, -3,  0, -1 >      ElecPotential;
      typedef Units< 1,  2, -4,  0, -2 >      Capacitance;
      typedef Units< 1,  2, -3,  0, -2 >      Resistance;
      typedef Units<-1, -2,  3,  0,  2 >      Conductance;

#else

      // Fancy Units template becomes just a scalar
      typedef double Units;

      //--------------------------------
      // BASE MEASURES

      typedef Units      Mass;
      typedef Units      Length;
      typedef Units      Time;
      typedef Units      Temperature;
      typedef Units      Current;

      //--------------------------------
      // DERIVED MEASURES
      
      typedef Units      Area;
      typedef Units      Volume;
      typedef Units      Force;
      typedef Units      Pressure;
      typedef Units      Velocity;
      typedef Units   Acceleration;
      typedef Units      Torque;
      typedef Units      Energy;
      typedef Units      Power;

      typedef Units   SpecificEnergy;
      typedef Units   Entropy;
      typedef Units   SpecificEntropy;

      typedef Units      Flowrate;

      // Electrical
      typedef Units      Charge;
      typedef Units      ElecPotential;
      typedef Units      Capacitance;
      typedef Units      Resistance;
      typedef Units      Conductance;


#endif  // CHECK_UNITS

//----------------------------------------------
// Define category bases

const Mass kilogram=Mass(1.0);
const Length metre=Length(1.0);
const Time second=Time(1.0);
const Temperature Kelvin=Temperature(1.0);
const Current ampere=Current(1.0);

//------------------------------------
// SOME ALTERNATIVE NAMES

typedef Velocity Speed;
typedef Length Distance;
typedef Energy Heat;
typedef Heat Work;
typedef Pressure Stress;

//------------------------------------
// SI MULTIPLIERS

const double Tera=1e12;
const double Giga=1e9;
const double Mega=1e6;
const double kilo=1e3;
const double hecta=1e2;
const double Deca=10;
const double deci=0.1;
const double centi=1e-2;
const double milli=1e-3;
const double micro=1e-6;

//------------------------------------
// COMMON MEASURES (SI)

const Mass gram = milli * kilogram;

const Length centimetre = metre / 100.0;
const Length kilometre = 1000.0 * metre;

const Area metre2 = metre * metre;
const Area hectare = (100*metre)*(100*metre);

const Volume metre3 = metre2 * metre;
const Volume litre = milli * metre3;
const Volume centimetre3 = (centi*metre)*(centi*metre)*(centi*metre);

const Time minute = 60.0 * second;
const Time hour= 60.0 * minute;
const Time day = 24.0 * hour;

const Force Newton = kilogram * metre / ( second * second );

const Pressure Pascal = Newton / (metre * metre );
const Pressure bar = 100.0 * kilo * Pascal;
const Pressure MPa = Mega * Pascal;
const Energy Joule = Newton * metre;

const Power Watt = Joule / second;

//const ElecPotential volt = Watt / ampere;
//const Charge Coulomb = ampere * second;
//const Capacitance Farad = volt / Coulomb;
//const Resistance Ohm = volt / ampere;

//------------------------------------
// COMMON MEASURES (NON-SI)

// ...

//------------------------------------
// THERMODYNAMIC MEASURES

const SpecificEnergy kJ_kg = kilo * Joule / kilogram;
const SpecificEntropy kJ_kgK = kilo * Joule / kilogram / Kelvin;

#endif // UNITS_H


----------------------------------------8<--------------------------------
Avatar of andrewjb
andrewjb
Flag of United Kingdom of Great Britain and Northern Ireland image

By the way, don't forget that

Centigrade + Farenheit isn't allowed either.... :-)
To get it compiled i had to change the following:

1. Turned all integer factors to double, e. g.  (metre*100) --> (metre*100.)

2. Added member function getval()

         double getval() const { return val; }

   and changed u.val by u.getval(). (my compiler didn't allow access to private
   member if there was a change in the template parameters).

3. In template operator/ () function i had to change

       Units<M-m, L-l, T-t, K-k, I-i> r;

    to

       Units<M-m, L-l, T-t, K-k, I-i> r(u.getval());

Regards, Alex
       
Avatar of jdpipe

ASKER

Cheers Alex, I'll give that a whiz and get back to you.

Andrewjb... yes, I had thought of that. Celcius to Fahrenheit are a problem and so is Kelvin to Celcius because that's an additive conversion, not a multiplication factor like all the others. C to F likewise I guess... don't know how I'll tackle that one! Probably need to specialise the Units class somehow.

JP
Avatar of jdpipe

ASKER

Hi Alex,

Still have trouble with this. I made the changes you suggested, but I'm still seeing the same problem. I'm not 100% sure that I understood your suggestions right, maybe you could have one more look? Also, what compiler did you try with?

I've had more success moving the operators out of the class template into their own method templates, but it's less succinct so I'd prefer this syntax from Rettig if possible.

If you could send back the code as it was when you got it to compile - that would probably show me where my mistake was... hopefully it's not a quirk of the GCC compiler...!?!

JP
I found out i made another change in units.h, that seems to be crucial:

It's a forward declaration of class Units.

If i omit it i get similar error messages than you. I guess it is the template operator * that would need it.

The compiler is VC6 on NT 4.0, actually not known to be very compliant...

Regards, Alex

//---------------------------------------------------------

#include "units.h"

#include <iostream>
#include <iomanip>
//#include <getopt.h>  // DIDN'T HAVE IT BUT DIDN'T NEED IT
using namespace std;

int main(int argc, char *argv[]){

     try{
     
          Velocity v = 60. * kilometre / hour;  // Made 60.
         
          Length x = 250. * kilometre;           // Same 250.
         
          Time t = x / v;
         
          cerr << "The time taken to travel " << x << " at speed " << v << " is " << t << endl;
         
     }catch(...){
          cerr << "FATAL ERROR: unknown";
          exit(1);
     }
     return 0;
}

//----------------------------------------------------

#ifndef UNITS_H
#define UNITS_H

#define CHECK_UNITS

#ifdef CHECK_UNITS

     // forward declaration !!!!
     template<int M,int L,int T, int K, int I>
     class Units;

     // All of the reinterpret casts are work-arounds to let us make
     // val & Units(double) private without using friends
     
     /**
          Purpose:
           Template class to create a 'Units' data type.
            It checks that units are correct at *COMPILE* time.
            It hides conversion constants.
            It enforces self documenting code.

          Compilers known compliant:
               . GNU gcc 3.3.1 (cygming special)
     */
     template<int M,int L,int T, int K, int I>
     class Units {

          public:
               
               /// Initialiser
               Units(double v) : val(v){}
               
               /// Copy constructor
               Units( const Units& u ) : val( u.val ){}
               
               ///     Assignment operator
               const Units& operator=( const Units& u ){     val=u.val; return *this; }

               // MULTIPLICATION AND DIVISION BY SCALARS
               
               Units operator*( double d ) const { return val*d; }
               Units operator/( double d ) const { return val/d; }
               const Units& operator*=( double d ) { val*=d; return *this; }
               const Units& operator/=( double d ) { val/=d; return *this; }

               // ADDITION/SUBTRACTION WITH UNITS
               
               Units operator+( const Units& u ) const {     return val+u.val; }
               Units operator-( const Units& u ) const {     return val-u.val; }
               Units& operator+=( const Units& u ) { val+=u.val; return *this; }
               Units& operator-=( const Units& u ) { val-=u.val; return *this; }
               Units operator-() const { return -val; }

               // COMPARATIVE OPERATORS
               
               bool operator==( const Units& u ) const { return val==u.val; }
               bool operator!=( const Units& u ) const { return val!=u.val; }
               bool operator< ( const Units& u ) const { return val< u.val; }
               bool operator<=( const Units& u ) const { return val<=u.val; }
               bool operator> ( const Units& u ) const { return val> u.val; }
               bool operator>=( const Units& u ) const { return val>=u.val; }

               double getval() const { return val; }
               /// Cast to scalar
               /**
                    Not defined here because only unitless can cast to double
               */
               operator double() const;

               ///     Multiplication with units
               template< int mm, int ll, int tt, int kk, int ii >
               Units< M+mm, L+ll, T+tt, K+kk, I+ii >
               operator*( const Units<mm,ll,tt,kk,ii>& u ) const {
                    //Units<M+m, L+l, T+t, K+k, I+i> r;
                    //*reinterpret_cast<double*>(&r) = val * *reinterpret_cast<const double*>(&u);
                    return Units<M+mm,L+ll,T+tt,K+kk,I+ii>(u.getval());
                    //return r;
               }

               /// Division with units
               template< int m, int l, int t, int k, int i >
               Units< M-m, L-l, T-t, K-k, I-i>
               operator/( const Units<m,l,t,k,i>& u ) const {
                    Units<M-m, L-l, T-t, K-k, I-i> r(u.getval());
                    *reinterpret_cast<double*>(&r) = val / *reinterpret_cast<const double*>(&u);
                    return r;
               }

          private:

               double val;

               // used by */+- to make returning values easy
               //Units( double v ):setval(v){}

     };


     /// Casting to double
     /**
          Only defined for unitless types
     */
     inline Units<0,0,0,0,0>::operator double() const {
          return val;
     }
     
     /// Scalar multiplication
     template<int M, int L, int T, int K, int I>
     inline Units< M, L, T, K, I >
     operator*( double d, const Units< M, L, T, K, I > &u) {
          return u*d;
     }

     /// Scalar division
     template<int M, int L, int T, int K, int I >
     inline Units< -M, -L, -T, -K, -I >
     operator/( double d, const Units< M, L, T, K, I >& u) {
          Units< -M, -L, -T, -K, -I > r;
          *reinterpret_cast<double*>(&r) = d / *reinterpret_cast<const double*>(&u);
          return r;
     }

     //--------------------------------
     // BASE MEASURES
     
     typedef Units< 1,  0,  0,  0,  0 >     Mass;
     typedef Units< 0,  1,  0,  0,  0 >     Length;
     typedef Units< 0,  0,  1,  0,  0 >     Time;
     typedef Units< 0,  0,  0,  1,  0 >     Temperature;
     typedef Units< 0,  0,  0,  0,  1 >     Current;

     //--------------------------------
     // DERIVED MEASURES
     
     typedef Units< 0,  2,  0,  0,  0 >     Area;
     typedef Units< 0,  3,  0,  0,  0 >     Volume;
     typedef Units< 1,  1, -2,  0,  0 >     Force;
     typedef Units< 1, -1, -2,  0,  0 >     Pressure;
     typedef Units< 0,  1, -1,  0,  0 >     Velocity;
     typedef Units< 0,  1, -2,  0,  0 >  Acceleration;

     typedef Units< 1,  2, -2,  0,  0 >     Torque;
     typedef Units< 1,  2, -2,  0,  0 >     Energy;
     typedef Units< 1,  2, -3,  0,  0 >     Power;

     typedef Units< 0,  2, -2,  0,  0 >     SpecificEnergy;
     typedef Units< 1,  2, -2, -1,  0 >  Entropy;
     typedef Units< 0,  2, -2, -1,  0 >  SpecificEntropy;
     
     typedef Units< 1,  1, -3,  0, -1 >  Conductivity;
     
     typedef Units< 0,  3, -1,  0,  0 >     Flowrate;

     // Electrical
     typedef Units< 0,  0,  1,  0,  1 >     Charge;
     typedef Units< 1,  2, -3,  0, -1 >     ElecPotential;
     typedef Units< 1,  2, -4,  0, -2 >     Capacitance;
     typedef Units< 1,  2, -3,  0, -2 >     Resistance;
     typedef Units<-1, -2,  3,  0,  2 >     Conductance;

#else

     // Fancy Units template becomes just a scalar
     typedef double Units;

     //--------------------------------
     // BASE MEASURES

     typedef Units     Mass;
     typedef Units     Length;
     typedef Units     Time;
     typedef Units     Temperature;
     typedef Units     Current;

     //--------------------------------
     // DERIVED MEASURES
     
     typedef Units     Area;
     typedef Units     Volume;
     typedef Units     Force;
     typedef Units     Pressure;
     typedef Units     Velocity;
     typedef Units   Acceleration;
     typedef Units     Torque;
     typedef Units     Energy;
     typedef Units     Power;

     typedef Units   SpecificEnergy;
     typedef Units   Entropy;
     typedef Units   SpecificEntropy;

     typedef Units     Flowrate;

     // Electrical
     typedef Units     Charge;
     typedef Units     ElecPotential;
     typedef Units     Capacitance;
     typedef Units     Resistance;
     typedef Units     Conductance;


#endif  // CHECK_UNITS

//----------------------------------------------
// Define category bases

const Mass kilogram=Mass(1.0);
const Length metre=Length(1.0);
const Time second=Time(1.0);
const Temperature Kelvin=Temperature(1.0);
const Current ampere=Current(1.0);

//------------------------------------
// SOME ALTERNATIVE NAMES

typedef Velocity Speed;
typedef Length Distance;
typedef Energy Heat;
typedef Heat Work;
typedef Pressure Stress;

//------------------------------------
// SI MULTIPLIERS

const double Tera=1e12;
const double Giga=1e9;
const double Mega=1e6;
const double kilo=1e3;
const double hecta=1e2;
const double Deca=10;
const double deci=0.1;
const double centi=1e-2;
const double milli=1e-3;
const double micro=1e-6;

//------------------------------------
// COMMON MEASURES (SI)

const Mass gram = milli * kilogram;

const Length centimetre = metre / 100.0;
const Length kilometre = 1000.0 * metre;

const Area metre2 = metre * metre;
const Area hectare = (metre * 100.) * (metre * 100.);

const Volume metre3 = metre2 * metre;
const Volume litre = milli * metre3;
const Volume centimetre3 = (centi*metre)*(centi*metre)*(centi*metre);

const Time minute = 60.0 * second;
const Time hour= 60.0 * minute;
const Time day = 24.0 * hour;

const Force Newton = kilogram * metre / ( second * second );

const Pressure Pascal = Newton / (metre * metre );
const Pressure bar = 100.0 * kilo * Pascal;
const Pressure MPa = Mega * Pascal;
const Energy Joule = Newton * metre;

const Power Watt = Joule / second;

//const ElecPotential volt = Watt / ampere;
//const Charge Coulomb = ampere * second;
//const Capacitance Farad = volt / Coulomb;
//const Resistance Ohm = volt / ampere;

//------------------------------------
// COMMON MEASURES (NON-SI)

// ...

//------------------------------------
// THERMODYNAMIC MEASURES

const SpecificEnergy kJ_kg = kilo * Joule / kilogram;
const SpecificEntropy kJ_kgK = kilo * Joule / kilogram / Kelvin;

#endif // UNITS_H

//---------------------------------------------------------

I made the forward declaration because i tried to add a friend declaration of the template class itself.

   friend class Units<M, L, T, K, I>;

The friend should help to access the private data member, but it doesn't work. So, i removed the friend declaration but not the forward declaration.

Regards, Alex
Avatar of jdpipe

ASKER

Hi Alex

I tried your code verbatim but it still gives those errors with GCC. The following implementation works in GCC, with the operators defined outside the class. Some problem with the nested templates, has to be.

Try this with your compiler and if it works then I'm happy.

Cheers
JP

-----------------------------------8<----------------------------
/// UNIT CHECKING / TRACKING CLASS
/**
      Adapted from code given by Rettig at
      http://www.embedded.com/shared/printableArticle.jhtml?articleID=9900094
      
      Code (currently) downloadable from
      http://www.embedded.com/code/2001code.htm
      
      Original Author:  Christopher Rettig ( rettigcd@bigfoot.com )
*/

#ifndef UNITS_H
#define UNITS_H

#include <iostream>
#include <cmath>

#define CHECK_UNITS

#ifdef CHECK_UNITS

      // All of the reinterpret casts are work-arounds to let us make
      // val & Units(double) private without using friends
      
      /**
            Purpose:
             Template class to create a 'Units' data type.
              It checks that units are correct at *COMPILE* time.
              It hides conversion constants.
              It enforces self documenting code.

            Dev platform:
                  . GNU gcc 3.3.1 (cygming special)
      */
      template<int M,int L,int T, int K, int I>
      class Units {

            public:
                  
                  Units(){
                        val=0;
                  }
                  
                  /// Initialiser
                  Units(double v){
                        setValue(v);
                  }
                  
                  /// Copy constructor
                  Units( const Units& u ) : val( u.getValue() ){}
                  
                  ///      Assignment operator
                  const Units& operator=( const Units& u ){      val=u.getValue(); return *this; }

                  // MULTIPLICATION AND DIVISION BY SCALARS
                  
                  const Units& operator*=( double d ) { val*=d; return *this; }
                  const Units& operator/=( double d ) { val/=d; return *this; }

                  // ACCUMULATION AND DIMINUTION
                  
                  Units& operator+=( const Units& u ) { val+=u.getValue(); return *this; }
                  Units& operator-=( const Units& u ) { val-=u.getValue(); return *this; }

                  /// Cast to scalar
                  /**
                        Not defined here because only Units<0,0,0,0,0> can cast to double
                  */
                  operator double() const;
                  
                  double getValue() const {
                        return val;
                  }
                  
                  // Value checking
                  bool isValid(){
                        return !isnan(val) && !isinf(val);
                  }
                  
                  bool isnan(){
                        return isnan(val);
                  }
                  
                  bool isinf(){
                        return isinf(val);
                  }
            private:

                  double val;

                  // used by */+- to make returning values easy
                  void setValue(double v){
                        val=v;
                  }

      };


      /// Casting to double
      /**
            Only defined for unitless types
      */
      inline Units<0,0,0,0,0>::operator double() const {
            return val;
      }
            
      // MULTIPLICATION
      
      template<int M, int L, int T, int K, int I,  int m, int l, int t, int k, int i>
      inline
      Units<M+m, L+l, T+t, K+k, I+i>
      operator *(const Units<M,L,T,K,I> u,const Units<m,l,t,k,i> v){
            return Units<M+m, L+l, T+t, K+k, I+i>(u.getValue() * v.getValue());
      }

      template<int m, int l, int t, int k, int i>
      inline
      Units<m, l, t, k, i>
      operator *(double u,const Units<m,l,t,k,i> v){
            return Units<m, l, t, k, i>(u * v.getValue());
      }

      template<int M, int L, int T, int K, int I>
      inline
      Units<M, L, T, K, I>
      operator *(const Units<M,L,T,K,I> u,double v){
            return Units<M, L, T, K, I>(u.getValue() * v);
      }

      template<int m, int l, int t, int k, int i>
      inline
      Units<m, l, t, k, i>
      operator *(int u,const Units<m,l,t,k,i> v){
            return Units<m, l, t, k, i>(u * v.getValue());
      }
      
      template<int M, int L, int T, int K, int I>
      inline
      Units<M, L, T, K, I>
      operator *(const Units<M,L,T,K,I> u,int v){
            return Units<M, L, T, K, I>(u.getValue() * v);
      }      
      // DIVISION
      
      template<int M, int L, int T, int K, int I,  int m, int l, int t, int k, int i>
      inline
      Units<M-m, L-l, T-t, K-k, I-i>
      operator/(const Units<M,L,T,K,I> u,const Units<m,l,t,k,i> v){
            return Units<M-m, L-l, T-t, K-k, I-i>(u.getValue() / v.getValue());
      }
      
      template<int M, int L, int T, int K, int I>
      inline
      Units<M, L, T, K, I>
      operator/(const Units<M,L,T,K,I> u,double v){
            return Units<M, L, T, K, I>(u.getValue() / v);
      }
      
      template<int m, int l, int t, int k, int i>
      inline
      Units<-m, -l, -t, -k, -i>
      operator/(double u,const Units<m,l,t,k,i> v){
            return Units<-m, -l, -t, -k, -i>(u / v.getValue());
      }

      template<int m, int l, int t, int k, int i>
      inline
      Units<-m, -l, -t, -k, -i>
      operator/(int u,const Units<m,l,t,k,i> v){
            return Units<-m, -l, -t, -k, -i>(u / v.getValue());
      }            

      template<int M, int L, int T, int K, int I>
      inline
      Units<M, L, T, K, I>
      operator/(const Units<M,L,T,K,I> u,int v){
            return Units<M, L, T, K, I>(u.getValue() / v);
      }
      // COMPARISON
      
      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator==(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()==v.getValue();
      }

      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator!=(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()!=v.getValue();
      }

      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator<(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()<v.getValue();
      }

      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator<=(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()<=v.getValue();
      }
      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator>=(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()>=v.getValue();
      }      
      
      // ADDITION AND SUBTRACTION
      
      template<int M, int L, int T, int K, int I>
      Units<M,L,T,K,I>
      operator+(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return Units<M,L,T,K,I>(u.getValue()+v.getValue());
      }

      template<int M, int L, int T, int K, int I>
      Units<M,L,T,K,I>
      operator-(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return Units<M,L,T,K,I>(u.getValue()-v.getValue());
      }

      template<int M, int L, int T, int K, int I>
      Units<M,L,T,K,I>
      operator-(const Units<M,L,T,K,I> u){
            return Units<M,L,T,K,I>(-u.getValue());
      }
      
      // OUTPUT
      
      template<int m, int l, int t, int k, int i>
      inline
      std::ostream& operator <<(std::ostream &os,const Units<m,l,t,k,i> &u){
            os << u.getValue();
            os.flags() & std::ios_base::showbase && os << " (dim)";
            return os;
      }

      #define DEFINE_OUTPUT_METHOD(MM,LL,TT,KK,II,UNITS) \
            inline \
            std::ostream& operator <<(std::ostream &os,const Units<MM,LL,TT,KK,II> &u){ \
                  os << u.getValue(); \
                  os.flags() & std::ios_base::showbase && os << " " << UNITS; \
                  return os; \
            }
      
      DEFINE_OUTPUT_METHOD(0,1,0,0,0,"m");
      DEFINE_OUTPUT_METHOD(0,0,1,0,0,"s");
      DEFINE_OUTPUT_METHOD(0,1,-1,0,0,"m/s");
      DEFINE_OUTPUT_METHOD(1,0,0,0,0,"kg");
      DEFINE_OUTPUT_METHOD(0,0,0,1,0,"K");
            
      //--------------------------------
      // BASE MEASURES
      
      typedef Units< 1,  0,  0,  0,  0 >      Mass;
      typedef Units< 0,  1,  0,  0,  0 >      Length;
      typedef Units< 0,  0,  1,  0,  0 >      Time;
      typedef Units< 0,  0,  0,  1,  0 >      Temperature;
      typedef Units< 0,  0,  0,  0,  1 >      Current;

      //--------------------------------
      // DERIVED MEASURES
      
      typedef Units< 0,  2,  0,  0,  0 >      Area;
      typedef Units< 0,  3,  0,  0,  0 >      Volume;
      typedef Units< 1,  1, -2,  0,  0 >      Force;
      typedef Units< 1, -1, -2,  0,  0 >      Pressure;
      typedef Units< 0,  1, -1,  0,  0 >      Velocity;
      typedef Units< 0,  1, -2,  0,  0 >  Acceleration;

      typedef Units< 1,  2, -2,  0,  0 >      Torque;
      typedef Units< 1,  2, -2,  0,  0 >      Energy;
      typedef Units< 1,  2, -3,  0,  0 >      Power;

      typedef Units< 0,  2, -2,  0,  0 >      SpecificEnergy;
      typedef Units< 1,  2, -2, -1,  0 >  Entropy;
      typedef Units< 0,  2, -2, -1,  0 >  SpecificEntropy;
      typedef Units< 1, -3,  0,  0,  0 >  Density;
      typedef Units<-1,  3,  0,  0,  0 >  SpecificVolume;
      typedef Units< 1, -1, -1,  0,  0>   DynamicViscosity;
      
      typedef Units< 1,  1, -3, -1,  0 >  Conductivity;
      
      typedef Units< 0,  3, -1,  0,  0 >      Flowrate;

      // Electrical
      typedef Units< 0,  0,  1,  0,  1 >      Charge;
      typedef Units< 1,  2, -3,  0, -1 >      ElecPotential;
      typedef Units< 1,  2, -4,  0, -2 >      Capacitance;
      typedef Units< 1,  2, -3,  0, -2 >      Resistance;
      typedef Units<-1, -2,  3,  0,  2 >      Conductance;

#else

      // Fancy Units template becomes just a scalar
      typedef double Units;

      //--------------------------------
      // BASE MEASURES

      typedef Units      Mass;
      typedef Units      Length;
      typedef Units      Time;
      typedef Units      Temperature;
      typedef Units      Current;

      //--------------------------------
      // DERIVED MEASURES
      
      typedef Units      Area;
      typedef Units      Volume;
      typedef Units      Force;
      typedef Units      Pressure;
      typedef Units      Velocity;
      typedef Units   Acceleration;
      typedef Units      Torque;
      typedef Units      Energy;
      typedef Units      Power;

      typedef Units   SpecificEnergy;
      typedef Units   Entropy;
      typedef Units   SpecificEntropy;
      typedef Units      Density;
      typedef Units   SpecificVolume;
      typedef Units   DynamicViscosity;

      typedef Units      Flowrate;

      // Electrical
      typedef Units      Charge;
      typedef Units      ElecPotential;
      typedef Units      Capacitance;
      typedef Units      Resistance;
      typedef Units      Conductance;


#endif  // CHECK_UNITS

//----------------------------------------------
// BASE UNITS FOR BASE MEASURES

const Mass kilogram=Mass(1.0);
const Length metre=Length(1.0);
const Time second=Time(1.0);
const Temperature Kelvin=Temperature(1.0);
const Current ampere=Current(1.0);

//------------------------------------
// SOME ALTERNATIVE NAMES

typedef Velocity Speed;
typedef Length Distance;
typedef Energy Heat;
typedef Heat Work; // nice
typedef Pressure Stress;

//------------------------------------
// SI MULTIPLIERS

const double Tera=1e12;
const double Giga=1e9;
const double Mega=1e6;
const double kilo=1e3;
const double hecta=1e2;
const double Deca=10;
const double deci=0.1;
const double centi=1e-2;
const double milli=1e-3;
const double micro=1e-6;

//------------------------------------
// COMMON MEASURES (SI)

const Mass gram = milli * kilogram;
const Mass kg = kilogram;

const Length centimetre = metre / 100.0;
const Length kilometre = 1000.0 * metre;

const Area metre2 = metre*metre;
const Area hectare = (100.0*metre)*(100.0*metre);

const Volume metre3 = metre2 * metre;
const Volume litre = milli * metre3;
const Volume centimetre3 = (centi*metre)*(centi*metre)*(centi*metre);

const Time minute = 60.0 * second;
const Time hour= 60.0 * minute;
const Time day = 24.0 * hour;

const Force Newton = kilogram * metre / ( second * second );

const Pressure Pascal = Newton / (metre * metre );
const Pressure bar = 100.0 * kilo * Pascal;
const Pressure MPa = Mega * Pascal;
const Energy Joule = Newton * metre;

const Power Watt = Joule / second;

//const ElecPotential volt = Watt / ampere;
//const Charge Coulomb = ampere * second;
//const Capacitance Farad = volt / Coulomb;
//const Resistance Ohm = volt / ampere;

//------------------------------------
// COMMON MEASURES (NON-SI)

// ...

//------------------------------------
// THERMODYNAMIC MEASURES

const SpecificEnergy kJ_kg = kilo * Joule / kilogram;
const SpecificEntropy kJ_kgK = kilo * Joule / kilogram / Kelvin;

//------------------------------------
// HANDLING TEMPERATURE CONVERSIONS

//...


#endif // UNITS_H
ASKER CERTIFIED SOLUTION
Avatar of itsmeandnobodyelse
itsmeandnobodyelse
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of jdpipe

ASKER

I'm a bit baffled as to how exactly it works when you take away those extra operators. It does work though. Would love to know why now... What is getting cast to what, etc?

JP

Here's my final version, for now:
My test program is at the end

------------------------------------8<------------------------------
/// UNIT CHECKING / TRACKING CLASS
/**
      Adapted from code given by Rettig at
      http://www.embedded.com/shared/printableArticle.jhtml?articleID=9900094
      
      Code (currently) downloadable from
      http://www.embedded.com/code/2001code.htm
      
      Original Author:  Christopher Rettig ( rettigcd@bigfoot.com )
*/

#ifndef UNITS_H
#define UNITS_H

#include <iostream>
#include <cmath>

#define CHECK_UNITS

#ifdef CHECK_UNITS

      // All of the reinterpret casts are work-arounds to let us make
      // val & Units(double) private without using friends
      
      /**
            Purpose:
             Template class to create a 'Units' data type.
              It checks that units are correct at *COMPILE* time.
              It hides conversion constants.
              It enforces self documenting code.

            Dev platform:
                  . Win2k -- gcc version 3.3.1 (cygming special)
            
            Tested on:
                  . NT4.0 -- VC++ 6.0
                  . Win2k -- gcc version 3.2.3 (mingw special 20030504-1)
                  . Linus -- gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)
      */
      template<int M,int L,int T, int K, int I>
      class Units {

            public:
                  
                  Units(){
                        val=0;
                  }
                  
                  /// Initialiser
                  Units(double v){
                        setValue(v);
                  }
                  
                  /// Copy constructor
                  Units( const Units& u ) : val( u.getValue() ){}
                  
                  ///      Assignment operator
                  const Units& operator=( const Units& u ){      val=u.getValue(); return *this; }

                  // MULTIPLICATION AND DIVISION BY SCALARS
                  
                  const Units& operator*=( double d ) { val*=d; return *this; }
                  const Units& operator/=( double d ) { val/=d; return *this; }

                  // ACCUMULATION AND DIMINUTION
                  
                  Units& operator+=( const Units& u ) { val+=u.getValue(); return *this; }
                  Units& operator-=( const Units& u ) { val-=u.getValue(); return *this; }

                  /// Cast to scalar
                  /**
                        Not defined here because only Units<0,0,0,0,0> can cast to double
                  */
                  operator double() const;
                  
                  double getValue() const {
                        return val;
                  }
                  
                  // Value checking
                  bool isValid(){
                        return !isnan(val) && !isinf(val);
                  }
                  
                  bool isnan(){
                        return isnan(val);
                  }
                  
                  bool isinf(){
                        return isinf(val);
                  }
            private:

                  double val;

                  // used by */+- to make returning values easy
                  void setValue(double v){
                        val=v;
                  }

      };


      /// Casting to double
      /**
            Only defined for unitless types
      */
      inline Units<0,0,0,0,0>::operator double() const {
            return val;
      }
            
      // MULTIPLICATION
      
      template<int M, int L, int T, int K, int I,  int m, int l, int t, int k, int i>
      inline
      Units<M+m, L+l, T+t, K+k, I+i>
      operator *(const Units<M,L,T,K,I> u,const Units<m,l,t,k,i> v){
            return Units<M+m, L+l, T+t, K+k, I+i>(u.getValue() * v.getValue());
      }

      template<int m, int l, int t, int k, int i>
      inline
      Units<m, l, t, k, i>
      operator *(double u,const Units<m,l,t,k,i> v){
            return Units<m, l, t, k, i>(u * v.getValue());
      }

      template<int M, int L, int T, int K, int I>
      inline
      Units<M, L, T, K, I>
      operator *(const Units<M,L,T,K,I> u,double v){
            return Units<M, L, T, K, I>(u.getValue() * v);
      }

      template<int m, int l, int t, int k, int i>
      inline
      Units<m, l, t, k, i>
      operator *(int u,const Units<m,l,t,k,i> v){
            return Units<m, l, t, k, i>(u * v.getValue());
      }
      
      template<int M, int L, int T, int K, int I>
      inline
      Units<M, L, T, K, I>
      operator *(const Units<M,L,T,K,I> u,int v){
            return Units<M, L, T, K, I>(u.getValue() * v);
      }      
      // DIVISION
      
      template<int M, int L, int T, int K, int I,  int m, int l, int t, int k, int i>
      inline
      Units<M-m, L-l, T-t, K-k, I-i>
      operator/(const Units<M,L,T,K,I> u,const Units<m,l,t,k,i> v){
            return Units<M-m, L-l, T-t, K-k, I-i>(u.getValue() / v.getValue());
      }
      
      template<int M, int L, int T, int K, int I>
      inline
      Units<M, L, T, K, I>
      operator/(const Units<M,L,T,K,I> u,double v){
            return Units<M, L, T, K, I>(u.getValue() / v);
      }
      
      template<int m, int l, int t, int k, int i>
      inline
      Units<-m, -l, -t, -k, -i>
      operator/(double u,const Units<m,l,t,k,i> v){
            return Units<-m, -l, -t, -k, -i>(u / v.getValue());
      }

      template<int m, int l, int t, int k, int i>
      inline
      Units<-m, -l, -t, -k, -i>
      operator/(int u,const Units<m,l,t,k,i> v){
            return Units<-m, -l, -t, -k, -i>(u / v.getValue());
      }            

      template<int M, int L, int T, int K, int I>
      inline
      Units<M, L, T, K, I>
      operator/(const Units<M,L,T,K,I> u,int v){
            return Units<M, L, T, K, I>(u.getValue() / v);
      }
      // COMPARISON
      
      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator==(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()==v.getValue();
      }

      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator!=(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()!=v.getValue();
      }

      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator<(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()<v.getValue();
      }

      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator<=(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()<=v.getValue();
      }
      template<int M, int L, int T, int K, int I>
      inline
      bool
      operator>=(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return u.getValue()>=v.getValue();
      }      
      
      // ADDITION AND SUBTRACTION
      
      template<int M, int L, int T, int K, int I>
      Units<M,L,T,K,I>
      operator+(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return Units<M,L,T,K,I>(u.getValue()+v.getValue());
      }

      template<int M, int L, int T, int K, int I>
      Units<M,L,T,K,I>
      operator-(const Units<M,L,T,K,I> u,const Units<M,L,T,K,I> v){
            return Units<M,L,T,K,I>(u.getValue()-v.getValue());
      }

      template<int M, int L, int T, int K, int I>
      Units<M,L,T,K,I>
      operator-(const Units<M,L,T,K,I> u){
            return Units<M,L,T,K,I>(-u.getValue());
      }
      
      // OUTPUT
      
      template<int m, int l, int t, int k, int i>
      inline
      std::ostream& operator <<(std::ostream &os,const Units<m,l,t,k,i> &u){
            os << u.getValue();
            os.flags() & std::ios_base::showbase && os << " (dim)";
            return os;
      }

      inline
      std::ostream& operator <<(std::ostream &os,const Units<0,0,0,0,0> &u){
            os << u.getValue();
            return os;
      }

      #define DEFINE_OUTPUT_METHOD(MM,LL,TT,KK,II,UNITS) \
            inline \
            std::ostream& operator <<(std::ostream &os,const Units<MM,LL,TT,KK,II> &u){ \
                  os << u.getValue(); \
                  os.flags() & std::ios_base::showbase && os << " " << UNITS; \
                  return os; \
            }

      DEFINE_OUTPUT_METHOD(0,1,0,0,0,"m");
      DEFINE_OUTPUT_METHOD(0,0,1,0,0,"s");
      DEFINE_OUTPUT_METHOD(0,1,-1,0,0,"m/s");
      DEFINE_OUTPUT_METHOD(1,0,0,0,0,"kg");
      DEFINE_OUTPUT_METHOD(0,0,0,1,0,"K");
            
      //--------------------------------
      // BASE MEASURES
      
      typedef Units< 1,  0,  0,  0,  0 >      Mass;
      typedef Units< 0,  1,  0,  0,  0 >      Length;
      typedef Units< 0,  0,  1,  0,  0 >      Time;
      typedef Units< 0,  0,  0,  1,  0 >      Temperature;
      typedef Units< 0,  0,  0,  0,  1 >      Current;

      //--------------------------------
      // DERIVED MEASURES
      
      typedef Units< 0,  2,  0,  0,  0 >      Area;
      typedef Units< 0,  3,  0,  0,  0 >      Volume;
      typedef Units< 1,  1, -2,  0,  0 >      Force;
      typedef Units< 1, -1, -2,  0,  0 >      Pressure;
      typedef Units< 0,  1, -1,  0,  0 >      Velocity;
      typedef Units< 0,  1, -2,  0,  0 >  Acceleration;

      typedef Units< 1,  2, -2,  0,  0 >      Torque;
      typedef Units< 1,  2, -2,  0,  0 >      Energy;
      typedef Units< 1,  2, -3,  0,  0 >      Power;

      typedef Units< 0,  2, -2,  0,  0 >      SpecificEnergy;
      typedef Units< 1,  2, -2, -1,  0 >  Entropy;
      typedef Units< 0,  2, -2, -1,  0 >  SpecificEntropy;
      typedef Units< 1, -3,  0,  0,  0 >  Density;
      typedef Units<-1,  3,  0,  0,  0 >  SpecificVolume;
      typedef Units< 1, -1, -1,  0,  0>   DynamicViscosity;
      
      typedef Units< 1,  1, -3, -1,  0 >  Conductivity;
      
      typedef Units< 0,  3, -1,  0,  0 >      Flowrate;

      // Electrical
      typedef Units< 0,  0,  1,  0,  1 >      Charge;
      typedef Units< 1,  2, -3,  0, -1 >      ElecPotential;
      typedef Units< 1,  2, -4,  0, -2 >      Capacitance;
      typedef Units< 1,  2, -3,  0, -2 >      Resistance;
      typedef Units<-1, -2,  3,  0,  2 >      Conductance;

#else

      // Fancy Units template becomes just a scalar
      typedef double Units;

      //--------------------------------
      // BASE MEASURES

      typedef Units      Mass;
      typedef Units      Length;
      typedef Units      Time;
      typedef Units      Temperature;
      typedef Units      Current;

      //--------------------------------
      // DERIVED MEASURES
      
      typedef Units      Area;
      typedef Units      Volume;
      typedef Units      Force;
      typedef Units      Pressure;
      typedef Units      Velocity;
      typedef Units   Acceleration;
      typedef Units      Torque;
      typedef Units      Energy;
      typedef Units      Power;

      typedef Units   SpecificEnergy;
      typedef Units   Entropy;
      typedef Units   SpecificEntropy;
      typedef Units      Density;
      typedef Units   SpecificVolume;
      typedef Units   DynamicViscosity;

      typedef Units      Flowrate;

      // Electrical
      typedef Units      Charge;
      typedef Units      ElecPotential;
      typedef Units      Capacitance;
      typedef Units      Resistance;
      typedef Units      Conductance;


#endif  // CHECK_UNITS

//----------------------------------------------
// BASE UNITS FOR BASE MEASURES

const Mass kilogram=Mass(1.0);
const Length metre=Length(1.0);
const Time second=Time(1.0);
const Temperature Kelvin=Temperature(1.0);
const Current ampere=Current(1.0);

//------------------------------------
// SOME ALTERNATIVE NAMES

typedef Velocity Speed;
typedef Length Distance;
typedef Energy Heat;
typedef Heat Work; // nice
typedef Pressure Stress;

//------------------------------------
// SI MULTIPLIERS

const double Tera=1e12;
const double Giga=1e9;
const double Mega=1e6;
const double kilo=1e3;
const double hecta=1e2;
const double Deca=10;
const double deci=0.1;
const double centi=1e-2;
const double milli=1e-3;
const double micro=1e-6;

//------------------------------------
// COMMON MEASURES (SI)

const Mass gram = milli * kilogram;
const Mass kg = kilogram;

const Length centimetre = metre / 100.0;
const Length kilometre = 1000.0 * metre;

const Area metre2 = metre*metre;
const Area hectare = (100.0*metre)*(100.0*metre);

const Volume metre3 = metre2 * metre;
const Volume litre = milli * metre3;
const Volume centimetre3 = (centi*metre)*(centi*metre)*(centi*metre);

const Time minute = 60.0 * second;
const Time hour= 60.0 * minute;
const Time day = 24.0 * hour;

const Force Newton = kilogram * metre / ( second * second );

const Pressure Pascal = Newton / (metre * metre );
const Pressure bar = 100.0 * kilo * Pascal;
const Pressure MPa = Mega * Pascal;
const Energy Joule = Newton * metre;

const Power Watt = Joule / second;

//const ElecPotential volt = Watt / ampere;
//const Charge Coulomb = ampere * second;
//const Capacitance Farad = volt / Coulomb;
//const Resistance Ohm = volt / ampere;

//------------------------------------
// COMMON MEASURES (NON-SI)

// ...

//------------------------------------
// THERMODYNAMIC MEASURES

const SpecificEnergy kJ_kg = kilo * Joule / kilogram;
const SpecificEntropy kJ_kgK = kilo * Joule / kilogram / Kelvin;

//------------------------------------
// HANDLING TEMPERATURE CONVERSIONS

//...


#endif // UNITS_H

----------------------------------------------8<--------------------------------
#include "units.h"

#include <iostream>
#include <iomanip>
using namespace std;

int main(int argc, char *argv[]){

      try{
            
            cerr.flags(ios_base::showbase);
      
            Velocity v = 60 * kilometre / hour;
            
            Length x = 250 * kilometre;
            
            Time t = x / v;
            
            cerr << "The time taken to travel " << x << " at speed " << v << " is " << t << endl;
            
            Velocity vv = 10 * kilometre / hour;
            
            cerr << "What about if the speed is " << vv << " faster?" << endl;
            
            v+=vv;
            
            cerr << "ie " << v << endl;
            
            Time t2 = x / v;
            
            cerr << "Then it takes " << t << " to travel  " << x << endl;
            
            cerr << "That's an improvements of " << t-t2 << endl;
            
            cerr << "Or " << (t-t2)/hour << " h" << endl;

            cerr << "Or " << (t-t2)/minute<< " min" << endl;
            
            //cerr << " You can't do the following:" << endl;
            //Time t3 = v * v;
            
      }catch(...){
            cerr << "FATAL ERROR: unknown";
            exit(1);
      }

}
>> how exactly it works when you take away those extra operators


You defined operator double() for Units<0, 0, 0, 0, 0> only, while i had to define it for all Units (unresolved externals). However, i got problems with extra operators that have a double as argument as these operators now are ambigous (e. g. for "metre * metre", the compiler could take any of the operators as any metre object could be turned to a double or not). Because of the constructor that takes a double, my compiler could turn any double, e. g. the r-value "metre * metre" to a Units object.

So, the main differences GCC / VC6 seem to be:

- GCC allows partial implementation of operators (operator double)
- VC6 takes a double for a Units object (and vice versa) and was able to deduce the
  appropriate template parameters:

  Area metre2 = metre * metre;

is equivalent to

  Area metre2( (double)((double)metre) * (double)metre) );  


Regards, Alex

Avatar of jdpipe

ASKER

Hi Alex

Hmm that's a bit of a worry actually. If what you says is true then the class wouldn't be fulfilling its purpose in VC6, would it?

It needs to *only* allow cast to double in the unique case where the measurement is dimensionless. Otherwise, what's stopping you from adding metres to kilograms?

Does the commented-out section in the test prog throw a compilation error for you?

JP
Sorry, it seems that i doesn't work:

The following statement compiles and runs not throwing an exception:

       Length z = metre + kilogram;

I tried to go back to my first compiling version (the first i posted above), however - for any reason - i couldn't get it compiled (e. g. cannot find appropriate constructor for Units<0, 1, -1, 0, 0>).

I removed all declarations/definitions of operator double() and it compiled .... beside of two exceptions:

  Length z = metre + kilogram;  // Isn't that great !!!
  cerr << "The time taken to travel " << x << " at speed " << v << " is " << t << endl;

That can be repaired by

     template<int M, int L, int T, int K, int I>
     inline ostream& operator << (ostream& os, const Units<M, L, T, K, I>& u)
    { os << u.getval(); return os; }

Regards, Alex





Avatar of jdpipe

ASKER

Hey Alex

Send me your final version and I'll check it in GCC

Wish I could work out a nice way of doing unit-testing for compile-time error detection...

JP
Hi JP,

i almost forgot your last request because i were at a different location from FRI to SUN.

Below is my latest running code on a VC6 Windows NT platform. The main difference to your final version is, that i omitted operator double() (as it would spoil all my multiplications and divisions) and that i used only member operators both for multiplication and division.

Regards, Alex

#include "units.h"

#include <iostream>
#include <sstream>
#include <string>
#include <iomanip>
//#include <getopt.h>
using namespace std;


int main(int argc, char *argv[]){

     try{
     
          Velocity v = 60. * kilometre / hour;
         
          Length x = 250. * kilometre;
         
          Time t = x / v;

          Length z = metre + metre;  // metre + kilogram didn't compile

          Units<0, 0, 0, 0, 0> unitless = 123.4;   // that compiles

          z = unitless * z;       // seems to work fine
         
          cerr << "The time taken to travel " << x << " at speed " << v << " is " << t << endl;
         
     }catch(...){
          cerr << "FATAL ERROR: unknown";
          exit(1);
     }
     return 0;
}

//----------------------------------------------------

#ifndef UNITS_H
#define UNITS_H

#define CHECK_UNITS

#include <iostream>
using namespace std;

#ifdef CHECK_UNITS

     // forward declaration !!!!
     template<int M,int L,int T, int K, int I>
     class Units;

     // All of the reinterpret casts are work-arounds to let us make
     // val & Units(double) private without using friends
     
     /**
          Purpose:
           Template class to create a 'Units' data type.
            It checks that units are correct at *COMPILE* time.
            It hides conversion constants.
            It enforces self documenting code.

          Compilers known compliant:
               . GNU gcc 3.3.1 (cygming special)
     */
     template<int M,int L,int T, int K, int I>
     class Units {

          public:
               
               /// Initialiser
               Units(double v) : val(v){}
               
               /// Copy constructor
               Units( const Units& u ) : val( u.val ){}
               
               ///     Assignment operator
               const Units& operator=( const Units& u ){     val=u.val; return *this; }

               // MULTIPLICATION AND DIVISION BY SCALARS
               
               Units operator*( double d ) const { return val*d; }
               Units operator/( double d ) const { return val/d; }
               const Units& operator*=( double d ) { val*=d; return *this; }
               const Units& operator/=( double d ) { val/=d; return *this; }

               // ADDITION/SUBTRACTION WITH UNITS
               
               Units operator+( const Units& u ) const {     return val+u.val; }
               Units operator-( const Units& u ) const {     return val-u.val; }
               Units& operator+=( const Units& u ) { val+=u.val; return *this; }
               Units& operator-=( const Units& u ) { val-=u.val; return *this; }
               Units operator-() const { return -val; }

               // COMPARATIVE OPERATORS
               
               bool operator==( const Units& u ) const { return val==u.val; }
               bool operator!=( const Units& u ) const { return val!=u.val; }
               bool operator< ( const Units& u ) const { return val< u.val; }
               bool operator<=( const Units& u ) const { return val<=u.val; }
               bool operator> ( const Units& u ) const { return val> u.val; }
               bool operator>=( const Units& u ) const { return val>=u.val; }

               double getval() const { return val; }
               /// Cast to scalar
               /**
                    Not defined here because only unitless can cast to double
               */
               // operator double() const;

               ///     Multiplication with units
               template< int mm, int ll, int tt, int kk, int ii >
               Units< M+mm, L+ll, T+tt, K+kk, I+ii >
               operator*( const Units<mm,ll,tt,kk,ii>& u ) const {
                    //Units<M+m, L+l, T+t, K+k, I+i> r;
                    //*reinterpret_cast<double*>(&r) = val * *reinterpret_cast<const double*>(&u);
                    return Units<M+mm,L+ll,T+tt,K+kk,I+ii>(u.getval());
                    //return r;
               }

               /// Division with units
               template< int m, int l, int t, int k, int i >
               Units< M-m, L-l, T-t, K-k, I-i>
               operator/( const Units<m,l,t,k,i>& u ) const {
                    Units<M-m, L-l, T-t, K-k, I-i> r(u.getval());
                    *reinterpret_cast<double*>(&r) = val / *reinterpret_cast<const double*>(&u);
                    return r;
               }

          private:

               double val;

               // used by */+- to make returning values easy
               //Units( double v ):setval(v){}

     };


     /// Casting to double
     /**
          Only defined for unitless types
     inline Units<0,0,0,0,0>::operator double() const {
          return val;
     }
     */

     /// output overload
     template<int M, int L, int T, int K, int I>
     inline ostream& operator << (ostream& os, const Units<M, L, T, K, I>& u)
     { os << u.getval(); return os; }


     /// Scalar multiplication
     template<int M, int L, int T, int K, int I>
     inline Units< M, L, T, K, I >
     operator*( double d, const Units< M, L, T, K, I > &u) {
          return u*d;
     }

     /// Scalar division
     template<int M, int L, int T, int K, int I >
     inline Units< -M, -L, -T, -K, -I >
     operator/( double d, const Units< M, L, T, K, I >& u) {
          Units< -M, -L, -T, -K, -I > r;
          *reinterpret_cast<double*>(&r) = d / *reinterpret_cast<const double*>(&u);
          return r;
     }

     //--------------------------------
     // BASE MEASURES
     
     typedef Units< 1,  0,  0,  0,  0 >     Mass;
     typedef Units< 0,  1,  0,  0,  0 >     Length;
     typedef Units< 0,  0,  1,  0,  0 >     Time;
     typedef Units< 0,  0,  0,  1,  0 >     Temperature;
     typedef Units< 0,  0,  0,  0,  1 >     Current;

     //--------------------------------
     // DERIVED MEASURES
     
     typedef Units< 0,  2,  0,  0,  0 >     Area;
     typedef Units< 0,  3,  0,  0,  0 >     Volume;
     typedef Units< 1,  1, -2,  0,  0 >     Force;
     typedef Units< 1, -1, -2,  0,  0 >     Pressure;
     typedef Units< 0,  1, -1,  0,  0 >     Velocity;
     typedef Units< 0,  1, -2,  0,  0 >  Acceleration;

     typedef Units< 1,  2, -2,  0,  0 >     Torque;
     typedef Units< 1,  2, -2,  0,  0 >     Energy;
     typedef Units< 1,  2, -3,  0,  0 >     Power;

     typedef Units< 0,  2, -2,  0,  0 >     SpecificEnergy;
     typedef Units< 1,  2, -2, -1,  0 >  Entropy;
     typedef Units< 0,  2, -2, -1,  0 >  SpecificEntropy;
     
     typedef Units< 1,  1, -3,  0, -1 >  Conductivity;
     
     typedef Units< 0,  3, -1,  0,  0 >     Flowrate;

     // Electrical
     typedef Units< 0,  0,  1,  0,  1 >     Charge;
     typedef Units< 1,  2, -3,  0, -1 >     ElecPotential;
     typedef Units< 1,  2, -4,  0, -2 >     Capacitance;
     typedef Units< 1,  2, -3,  0, -2 >     Resistance;
     typedef Units<-1, -2,  3,  0,  2 >     Conductance;

#else

     // Fancy Units template becomes just a scalar
     typedef double Units;

     //--------------------------------
     // BASE MEASURES

     typedef Units     Mass;
     typedef Units     Length;
     typedef Units     Time;
     typedef Units     Temperature;
     typedef Units     Current;

     //--------------------------------
     // DERIVED MEASURES
     
     typedef Units     Area;
     typedef Units     Volume;
     typedef Units     Force;
     typedef Units     Pressure;
     typedef Units     Velocity;
     typedef Units   Acceleration;
     typedef Units     Torque;
     typedef Units     Energy;
     typedef Units     Power;

     typedef Units   SpecificEnergy;
     typedef Units   Entropy;
     typedef Units   SpecificEntropy;

     typedef Units     Flowrate;

     // Electrical
     typedef Units     Charge;
     typedef Units     ElecPotential;
     typedef Units     Capacitance;
     typedef Units     Resistance;
     typedef Units     Conductance;


#endif  // CHECK_UNITS

//----------------------------------------------
// Define category bases

const Mass kilogram=Mass(1.0);
const Length metre=Length(1.0);
const Time second=Time(1.0);
const Temperature Kelvin=Temperature(1.0);
const Current ampere=Current(1.0);

//------------------------------------
// SOME ALTERNATIVE NAMES

typedef Velocity Speed;
typedef Length Distance;
typedef Energy Heat;
typedef Heat Work;
typedef Pressure Stress;

//------------------------------------
// SI MULTIPLIERS

const double Tera=1e12;
const double Giga=1e9;
const double Mega=1e6;
const double kilo=1e3;
const double hecta=1e2;
const double Deca=10;
const double deci=0.1;
const double centi=1e-2;
const double milli=1e-3;
const double micro=1e-6;

//------------------------------------
// COMMON MEASURES (SI)

const Mass gram = milli * kilogram;

const Length centimetre = metre / 100.0;
const Length kilometre = 1000.0 * metre;

const Area metre2 = metre * metre;
const Area hectare = (metre * 100.) * (metre * 100.);

const Volume metre3 = metre2 * metre;
const Volume litre = milli * metre3;
const Volume centimetre3 = (centi*metre)*(centi*metre)*(centi*metre);

const Time minute = 60.0 * second;
const Time hour= 60.0 * minute;
const Time day = 24.0 * hour;

const Force Newton = kilogram * metre / ( second * second );

const Pressure Pascal = Newton / (metre * metre );
const Pressure bar = 100.0 * kilo * Pascal;
const Pressure MPa = Mega * Pascal;
const Energy Joule = Newton * metre;

const Power Watt = Joule / second;

//const ElecPotential volt = Watt / ampere;
//const Charge Coulomb = ampere * second;
//const Capacitance Farad = volt / Coulomb;
//const Resistance Ohm = volt / ampere;

//------------------------------------
// COMMON MEASURES (NON-SI)

// ...

//------------------------------------
// THERMODYNAMIC MEASURES

const SpecificEnergy kJ_kg = kilo * Joule / kilogram;
const SpecificEntropy kJ_kgK = kilo * Joule / kilogram / Kelvin;

#endif // UNITS_H

// units.h