Link to home
Start Free TrialLog in
Avatar of phoffric
phoffric

asked on

C++03 Units Type Safety Enforcement using Meta-Programming - Part 3

I have a short meta-programming approach for POD type safety from a previous question:
https://www.experts-exchange.com/questions/29056989/C-03-Templatize-Type-Safety-Enforcement-Part-2.html

I see the benefits of meta-programming as simplifying the development process for units - I believe that all new developers would have to do is to include a units header file, and follow a simple recipe for defining new data types.

I wrote in that question:
Other dimensional analysis areas that I am leaning towards:
 1. Make the class name part of the template
 2. Make the set of enumeration values part of the template so that they are not universally available
 3. Be able to operate on two different families of units. One obvious example would be:
        "distance (meters) = rate (meters/sec) * time (sec)".
     But we will have many other cross-unit operations as well.

I thought that I may as well see if I can get some tips on these ideas. If these 3 goals are realistic, but too much for one question, I am happy to work on what is reasonable for one question.

At the moment, I have to transcribe the previous question to my workstation, and then sell the ideas here and from the previous question to my lead software developer. The PM and Systems Engineer definitely like the idea of POD type safety. I just hope the lead likes the template approach.
Avatar of evilrix
evilrix
Flag of United Kingdom of Great Britain and Northern Ireland image

>> 1. Make the class name part of the template
Can you give an example as I'm not quite sure what you mean by this.

>> 2. Make the set of enumeration values part of the template so that they are not universally available
Do you mean part of the Frequency template class? If so, you can't as it's a dependent type that is required to derive the concrete type. You'd end up disappearing up your own exhaust pipe in a world of never ending recursion. Maybe an example would help clarify what you mean, if I have misunderstood?

 >> 3. Be able to operate on two different families of units. One obvious example would be:
        "distance (meters) = rate (meters/sec) * time (sec)".
     But we will have many other cross-unit operations as well.

I would do this using template policies. Define a template class that accepts two policies (template arguments), one denoting type and the other the units. For example:

typedef Units<Distance, Miles> DistanceMiles;
typedef Units<Distance, Yards> DistanceYards;
typedef Units<Distance, Feet> DistanceFeet;
typedef Units<Distance, Inches> DistanceInches;

Your Distance policy class will provide the mechanics for working with distance and your units policy class will provide the mechanics for working with imperial units (in this case - metric if you live in Europe, heh).

At this point, I'll leave you to digest this idea. Check out std::chrono::duration, as this does something similar for time duration (although it is slightly different, the idea is the same).

Policy driven programming, which implements concepts that model different mechanics is a very powerful design paradigm and well worth reading up about. The reading material I provided you in the  last question (especially the Modern C++ Design book) goes into this in great depth.
Avatar of phoffric
phoffric

ASKER

>> 1. Make the class name part of the template
>> >> Can you give an example as I'm not quite sure what you mean by this.

Frequency is one family of units. Other families are distance, speed, etc. I was thinking of doing this so that I might not have to write multiple templates for each family unit.
template <class FamilyUnit_T, ConvFactor CF>
class FamilyUnit_T
{ ... };

Open in new window


>> 2. If so, you can't as it's a dependent type that is required to derive the concrete type. You'd end up disappearing up your own exhaust pipe in a world of never ending recursion.
Oh yeah, the enums have to be external.

>> 3. I would do this using template policies. Define a template class that accepts two policies (template arguments), one denoting type and the other the units.
Can you point me to some online references to illustrate this point and technique?

Thanks,
Paul
i am not quite sure whether you want me to participate by promoting some ideas how you could naturally have "families" of units by using inheritance.

Make the class name part of the template
assume you have a baseclass for each family. then define and implement all these baseclasses by using a template class.

Make the set of enumeration values part of the template so that they are not universally available
with inheritance the families are not derived from enumerator types.

Be able to operate on two different families of units.
of course.

SpeedFamily SomeClass::CalculateSpeed(const DistanceFamily & dist, const TimeFamily & time) 
{
      return SpeedFamily((dist.Value()*dist.Factor())/(time.Value()*time.Factor()), dist.Factor()/time.Factor()));
}

Open in new window


Sara
1. Ah, I understand now. If you look at the previous example I've given you'll see this shouldn't be necessary since this would form part of the template's definition.

typedef Units<Frequency, Hz> FrequencyHz;
typedef Units<Frequency, KHz> FrequencyKHz;
typedef Units<Frequency, MHz> FrequencyMHz;
typedef Units<Frequency, GHz> FrequencyGHz;

2. Yeah, although if you are implementing policies you won't be using enums; rather, you'll be implementing Policy classes.

3. This Wikipedia page is a good place to start: https://en.wikipedia.org/wiki/Policy-based_design
I read the wiki page, and got the idea. Maybe I'll jump in tomorrow and see if I can define Units.
Do you think that if I want to have 3 units, say distance (meters), speed (meters/second), and time (seconds), such that
distance(meters) = speed(meters/second) * time (seconds)

Open in new window

that I'll need another mechanism other than Units to handle 3 different families of units?
Or, to get:
distance(meters) = speed(meters/second) * time (seconds)

Open in new window

maybe I just have to make a function:
Units<Distance, Meters>  computeDistance( 
                               Units<Speed, MetersPerSecond> speed, 
                               Units<Time, Second> time);

Open in new window

I bet there is a more general way to handle this merging of three different unit types.
No, that's where the policies come in, they handle that for you.
Starting to see a pattern with what you did in Part 2, in the Wiki, and chrono::duration -
// duration constructor
#include <iostream>
#include <ratio>
#include <chrono>

int main ()
{
  typedef std::chrono::duration<int> seconds_type;
  typedef std::chrono::duration<int,std::milli> milliseconds_type;
  typedef std::chrono::duration<int,std::ratio<60*60>> hours_type;

  hours_type h_oneday (24);                  // 24h
  seconds_type s_oneday (60*60*24);          // 86400s
  milliseconds_type ms_oneday (s_oneday);    // 86400000ms

  seconds_type s_onehour (60*60);            // 3600s
//hours_type h_onehour (s_onehour);          // NOT VALID (type truncates), use:
  hours_type h_onehour (std::chrono::duration_cast<hours_type>(s_onehour));
  milliseconds_type ms_onehour (s_onehour);  // 3600000ms (ok, no type truncation)

  std::cout << ms_onehour.count() << "ms in 1h" << std::endl;

  return 0;
}

Open in new window

So, I learned today that making really good policies is not trivial. Can you recommend any online tutorials that might give me some good pointers?
To be honest, I don't really know what online material you could use as most of what I know comes from reading the books I suggested to you and many years experience. I will say that there is no substitute for writing code!

The hardest part, I think, is getting over the idea that templates are just for implementing generic containers or functions. Yes, that can do that, but they can also implement policies and so getting your mind to think in that way and to also think of the C++ type system as an integral part of your code-flow (as in using types to make decisions are compile time) is the hardest part to get your head wrapped around.

When I was learning this stuff I just wrote loads of code to do simple things with templates, such as factorials, smart pointers with threading policies, classes to deal with "type traits" - something any meta-template programmer must understand -- and stuff like that. Start small and work your way up.

I've written you a simple(ish) example. The code is split in two parts (see the comments in the code). The top half is the generic template stuff that is reusable. The second half is the policies and a bit of code to demo everything. Although it looks complicated, it's actually not that bad and with a bit of reading you should be able to make sense of it.

A lot of the complexity comes with the need to deal with conversions between the types. The good thing is that the most complex part is the reusable code and once written (and debugged) it should just work.

The remaining code (the stuff the users of the library will write) is actually very simple (as the complex stuff is in the library code and, as such, this should be quite easy to use from an end-user of the library point of view. To put this into context, think how easy the STL is to use, but look at how complex even the vector class is when you read its implementation code.

I will say that I've not tested this thoroughly and so don't be surprised if you manage to find bugs (heh), but it should be enough to give you an idea. :)

Sara, I'd love to see your non-template version of this. It would be good for Paul to have a different perspective.

Anyway, here the code. I hope it helps.

// Library stuff ------------------------------------------------------------->
#include <iostream>

template <
   typename ValueType,
   ValueType VT,
   typename T
>
class Value
{
public:
   typedef T RawType;

   // default c_tor
   Value()
      : val_(RawType())
   {
   }

   // RawType c_tor
   explicit Value(RawType val)
      : val_(val)
   {
   }

   // like for like copy c_tor
   Value(Value const & other)
      : val_(other.val_)
   {
   }

   // apples vs. oranges copy c_tor, so perform conversion
   template <
      typename ValueTypeOther,
      ValueTypeOther VTOther,
      typename TOther
   >
   Value(Value<ValueTypeOther, VTOther, TOther> const & other)
      : val_(Convert(other).Get())
   {
   }

   // apples vs. oranges need converting - specialise to provide conversion
   template <
      typename ValueTypeOther,
      ValueTypeOther VTOther,
      typename TOther
   >
   static Value Convert(Value<ValueTypeOther, VTOther, TOther> const & other)
   {
      return other;
   }

   // Getter and setter for raw type
   RawType const & Get() const { return val_; }
   void Set(RawType const & val) { val_ = val; }

private:
   RawType val_;
};

template <
   typename U,
   typename V
>
class Unit : U
{
public:
   typedef U UnitType;
   typedef V ValueType;

   Unit()
      : val_(ValueType())
   {
   }

   Unit(Unit const & val)
      : val_(val.Get())
   {
   }

   Unit & operator =(Unit const & other)
   {
      val_ = other.Get();
      return *this;
   }

   template <
      typename UOther,
      typename VOther
   >
   Unit(Unit<UOther, VOther> const & other)
      : val_(other.Get())
   {
   }

   template <
      typename UOther,
      typename VOther
   >
   Unit & operator =(Unit<UOther, VOther> const & other)
   {
      val_ = other.Get();
      return *this;
   }

   Unit(ValueType const & val)
      : val_(val)
   {
   }

   Unit & operator =(ValueType const & val)
   {
      val_.Set(val);
      return *this;
   }

   template <
      typename ValueType,
      ValueType VT,
      typename T
   >
   Unit(Value<ValueType, VT, T> const & val)
      : val_(val)
   {
   }

   template <
      typename ValueType,
      ValueType VT,
      typename T
   >
   Unit & operator =(Value<ValueType, VT, T> const & val)
   {
      val_.Set(val);
      return *this;
   }

   Unit(typename ValueType::RawType const & val)
      : val_(val)
   {
   }

   Unit & operator =(typename ValueType::RawType const & val)
   {
      val_.Set(val);
   }

   Unit & operator ++()
   {
      UnitType::Increase(1);
      return *this;
   }

   Unit & operator +=(ValueType const & val)
   {
      UnitType::Increase(val_, val);
      return *this;
   }

   template <
      typename UOther,
      typename VOther
   >
   Unit & operator +=(Unit<UOther, VOther> const & val)
   {
      UnitType::Increase(val_, Unit(val).Get());
      return *this;
   }

   Unit & operator +=(typename ValueType::RawType const & val)
   {
      return *this += ValueType(val);
   }

   ValueType const & Get() const { return val_; }

private:
   ValueType val_;

};

template <
   typename ValueType,
   ValueType VT,
   typename T
>
std::ostream & operator << (
   std::ostream & out,
   Value<ValueType, VT, T> const & val
   )
{
   return out << val.Get();
}

template <
   typename U,
   typename V
>
std::ostream & operator << (
   std::ostream & out,
   Unit<U, V> const & unit
   )
{
   return out << unit.Get();
}

// My stuff ------------------------------------------------------------------>

enum SpeedType {
   MPH,
   KPH
};

typedef Value<SpeedType, MPH, double> Mph;
typedef Value<SpeedType, KPH, double> Kph;

// conversion functions
template <> template <>
Mph Mph::Convert(Kph const & kph)
{
   return Mph(kph.Get() * 0.621371192);
}

template <> template <>
Kph Kph::Convert(Mph const & mph)
{
   return Kph(mph.Get() * 1.609344);
}

class Speed
{
public:
   template <
      typename ValueType,
      ValueType VT,
      typename T
   >
   void Increase(
      Value<ValueType, VT, T> & val,
      Value<ValueType, VT, T> const & amt
   )
   {
      val.Set(val.Get() + amt.Get());
   }
};

typedef Unit<Speed, Mph> SpeedMph;
typedef Unit<Speed, Kph> SpeedKph;

int main()
{
   SpeedMph mph(50.0);
   SpeedKph kph(0);

   std::cout
      << mph << " mph" << std::endl
      << kph << " kph" << std::endl
      ;

   mph += 10.0;
   kph += mph;

   std::cout
      << mph << " mph" << std::endl
      << kph << " kph" << std::endl
      ;
}

Open in new window

don't know whether the following could be called a non-template version.

enum Family { Frequency, Distance, Time, Speed };

template 
<Family F>
class UnitBase
{
    double val_;
    double fct_;
protected:
    UnitBase<F>(const UnitBase<F> & rel, double f) 
        : val_(rel.NormVal()/f) 
        , fct_(f) 
    {}
public:
    explicit UnitBase<F>(double v, double f) : val_(v), fct_(f) {}
    
    double Value() const { return val_; }
    double Factor() const { return fct_; }
    double NormVal() const { return val_*fct_; }
    
    UnitBase<F> operator + (const UnitBase<F> & u) { return UnitBase<F>(((NormVal()+u.NormVal()) / fct_), fct_); }
    UnitBase<F> operator + (double d) { return UnitBase<F>((val_ + d / fct_), fct_); }
    UnitBase<F> & operator += (const UnitBase<F> & u) { val_ = ((NormVal()+u.NormVal()) / fct_); return *this; }
    UnitBase<F> & operator += (double d) { val_ += d; return *this;  }
    UnitBase<F> operator * (double d) { return UnitBase<F>(val_*d, fct_); }
    friend UnitBase<F> operator * (double d, const UnitBase<F> & u) { return UnitBase<F>(u.val_*d, u.fct_); }
    friend std::ostream & operator<< (std::ostream & os, const UnitBase<F> & u) { return os << u.val_; }
};

typedef UnitBase<Frequency> FrequencyBase;
typedef UnitBase<Distance>  DistanceBase;
typedef UnitBase<Time>      TimeBase;
typedef UnitBase<Speed>     SpeedBase;

class HZ : public FrequencyBase
{
public:
    static const double CF() { return  1.; }
    explicit HZ(double v) : FrequencyBase(v, CF()) {}     
    HZ(const FrequencyBase & other) : FrequencyBase(other, CF()) {}
};

class KHZ : public FrequencyBase
{
public:
    static const double CF() { return 1000; }
    explicit KHZ(double v) : FrequencyBase(v, CF()) {}     
    KHZ(const FrequencyBase & other) : FrequencyBase(other, CF()) {}
};

class KM : public DistanceBase
{
public:
    static const double CF() { return  1000.; }
    explicit KM(double v) : DistanceBase(v, CF()) {}     
    KM(const DistanceBase & other) : DistanceBase(other, CF()) {}
};

class MILE : public DistanceBase
{
public:
    static const double CF() { return 1609.344; }
    explicit MILE(double v) : DistanceBase(v, CF()) {}     
    MILE(const DistanceBase & other) : DistanceBase(other, CF()) {}
};

class SECOND : public TimeBase
{
public:
    static const double CF() { return  1.; }
    explicit SECOND(double v) : TimeBase(v, CF()) {}     
    SECOND(const TimeBase & other) : TimeBase(other, CF()) {}
};

class HOUR : public TimeBase
{
public:
    static const double CF() { return 3600; }
    explicit HOUR(double v) : TimeBase(v, CF()) {}     
    HOUR(const TimeBase & other) : TimeBase(other, CF()) {}
};

class KMH : public SpeedBase
{
public:
    static const double CF() { return  1.; }
    explicit KMH(double v) : SpeedBase(v, CF()) {}     
    KMH(const SpeedBase & other) : SpeedBase(other, CF()) {}
    explicit KMH(const DistanceBase & dist, const TimeBase & time) : SpeedBase((KM(dist).Value()/HOUR(time).Value()), CF()) {} 
};

class MPH : public SpeedBase
{                              
public:
    static const double CF() { return MILE::CF()/KM::CF(); }
    explicit MPH(double v) : SpeedBase(v, CF()) {}     
    MPH(const SpeedBase & other) : SpeedBase(other, CF()) {}
    explicit MPH(const DistanceBase & dist, const TimeBase & time) : SpeedBase(MPH(KMH(dist, time))) {}  
};


int main()
{
   KMH kph(100);
   MPH mph(kph);
   double dm = mph.NormVal();

   std::cout
      << kph << " kph" << std::endl
      << mph << " mph" << std::endl
      ;

   mph += 10.0;
   kph += mph;

   std::cout
      << mph << " mph" << std::endl
      << kph << " kph" << std::endl
      ;
               
    KM   km = KM(200);
    HOUR h(2);
    mph = MPH(km, h);

    std::cout
      << mph << " mph" << std::endl
      << km << " km" << std::endl
      << h << " hours" << std::endl
      ;
     
     return 0;
}               

Open in new window


Sara
Reading the books you noted sounds like the best idea for my getting into meta-programming. Thanks for the book selections.

Thank you both for presenting your styles for handling this topic in C++03.
I have printed out both programs, and will review and run them at home this week.
As before, it may take a day for the meta programming ideas to sink in.

In about 3 months we will be getting C++11 (no, not C++14).
But, at that time, we will have to survey all of our teams around the country to see whether they can handle C++11.
If not, we may have to stick with C++03.

The lead came back today, and I reviewed my test program. He asked me to write up an explanation as to how the program noted in the previous question works. I was wondering whether one of you would like to write an article on this subject. If not, then someday, once I feel more confident in this approach, I may write an article - it might even help me understand the subject matter better if I can explain it to someone else.

The lead's concern was what happens if he and I leave the group, and wonders whether the new developer will have enough background to handle changes to the template or policy based design. What if a new function that requires 10 different kinds of units is required. How will he be able to maintain the new capabilities. Hmm, I wonder how he would feel if we use in the future C++11 and decide to use its features. Maybe they should hire someone who is knowledgeable about the necessary C++11 features. I'll ask the lead as well as the Program Manager about that.
Paul, write an article and I'll edit it for you (I have Page Editor permissions). I am going on holiday in a couple of days so don't have time to write one atm. Sorry.
Ricky,

Enjoy your holiday! :)

Yikes!! I better review your code asap in case (- no make that - "when") I have questions.

BTW, In Part 2 of this series, your post had nice I/O that printed out the units. I was so busy trying to wrap my head around the template approach that I forgot about the easy parts. Still, nice to have. Thanks again for helping me get into this new area.

Paul
@evilrix, I have some questions?
0. In lines 12, 69, 70, there are typedefs that provide alias's to the class templates.
Could the class templates just be renamed to the typedef alias, and then throw away the typedef?

1.  line 253:  SpeedMph mph(50.0);
1a. line 139 ctor selected. Why that one?
1b. Why the typename in that ctor? How does "typename ValueType::RawType" help determine that ctor?
1c. And why the reference to ValueType::RawType?
1d. Then the Value explicit ctor at line 21 is called. Why is that ctor called?
1c. re: Unit<Speed, Mph> which is Unit<Speed, Value<SpeedType, MPH, double> >
>       then in line 63, I think U is Speed,     and V is  Value<SpeedType, MPH, double>
And I think for Value: ValueType is SpeedType; VT is MPH, and T is double.
I am curious as to why in line 6, there is VT, but VT is not used at all in class Value. Why is that?


2. Sara has at line 123: mph = MPH(km, h);, which I looks like will compute km/hour and then convert it to mph.
Could you please show how to do that with your code.

I am still confused by all of your code. Maybe after a couple of Q&A, things will become clearer. Thanks.
.
>> Could the class templates just be renamed to the typedef alias, and then throw away the typedef?
template <
   typename ValueType,
   ValueType VT,
   typename T
>
class Value
{
public:
   typedef T RawType;

Open in new window

Changed this to the following thinking that it is equivalent. But I then got compiler errors. Why aren't they equivalent.
template <
   typename ValueType,
   ValueType VT,
   typename RawType
>
class Value
{
public:

Open in new window

>> Could the class templates just be renamed to the typedef alias, and then throw away the typedef?

you could do:

template <
   typename ValueType,
   ValueType VT,
   //typename T
   typename RawType
>
class Value
{
public:
   //typedef T RawType;

   // default c_tor
   Value()
      : val_(RawType())
   {
   }

   // RawType c_tor
   explicit Value(RawType val)
      : val_(val)
   {
   }

   // like for like copy c_tor
   Value(Value const & other)
      : val_(other.val_)
   {
   }

   // apples vs. oranges copy c_tor, so perform conversion
   template <
      typename ValueTypeOther,
      ValueTypeOther VTOther,
      typename TOther
   >
   Value(Value<ValueTypeOther, VTOther, TOther> const & other)
      : val_(Convert(other).Get())
   {
   }

   // apples vs. oranges need converting - specialise to provide conversion
   template <
      typename ValueTypeOther,
      ValueTypeOther VTOther,
      typename TOther
   >
   static Value Convert(Value<ValueTypeOther, VTOther, TOther> const & other)
   {
      return other;
   }

   // Getter and setter for raw type
   RawType const & Get() const { return val_; }
   void Set(RawType const & val) { val_ = val; }

private:
   RawType val_;
};

Open in new window


what compiles but requires some additional template definitions above functions and constructors. also the expressions 'ValueType::RawType' or 'typename ValueType::RawType' must no longer be used. as template Code won't be compiled in total but only those parts used from sample program, it isn't quite sure that all my changes would do the same as the original.


so actually the typedef was not an overhead but allows shorter code using the template type. you better leave it as it was.

Sara
Hey Ricky,

Hope you have a great holiday. I thought about how to understand your code. I figured that since I understood Part 2 in my head, I could do the same here. Nope. I'll need to handle Part 3 differently. I'll work on it, and maybe have time to start reading one of your recommended books. Talk to you in a couple of weeks when you get back.

Paul
This question needs an answer!
Become an EE member today
7 DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform.
View membership options
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.