tags:

views:

276

answers:

5

I had an idea motivated by some of the documentation I read in the tutorial of the boost MTL library.

The basic premise is that I would like to use templates to give me compile time type checking errors for things that are otherwise the same.

Namely, lets say I have two units of measurement, Radians and Degrees. The most obvious way to go about getting type safety is to define 2 classes:

struct Radian
{
  float rad;
}

struct Degree
{
  float deg;
}

This is all well and good, except I can do something like

func()
{
  Radian r;
  Degree d;

  r.rad = d.deg;
}

It would be nice if I could flag an assignment like that as a compile time type error. This lead me to consider the following:

struct Degree {};
struct Radian {};

template <class Data, class Unit>
struct Quantity
{
    Data val;

    Quantity<Data,Unit>() : val() {}
    explicit Quantity<Data,Unit>(Data v) : val(v) {}
};

typedef Quantity<float,Radian> Rad;
typedef Quantity<float,Degree> Deg;

Now, the equivalent code of func() using the types Rad and Deg would flag that assignment as a compile time error (and with explicit set, even doing something as simple as Rad r = 2.0 is considered a compile time error).

What I really want is a float that has this additional units property that can be used to catch logic errors at compile time (ie, using degrees in a function that expects radians), but for all intents and purposes, these things are floats.

As a general question, what are your thoughts on this approach? I am slightly concerned that this is the wrong way to achieve my goal, but it has a strange appeal. Also, is there a "trait" or something like boost concept check that I can use to determine that Data is "float like". Finally, is there any way I can inherit default implementations of operations such as "<<" so that I don't have to implement all of them manually in the Quantity class?

+1  A: 

The Boost Template Metaprogramming Library documentation touches on this sort of thing: http://www.boost.org/doc/libs/1%5F40%5F0/libs/mpl/doc/tutorial/dimensional-analysis.html.

Managu
+6  A: 

I believe you are looking for something like Boost.Units. I think that's a place to get started.

rlbond
that is quite close to what I wanted :)
Voltaire
+2  A: 

The normal type system already does this:

Radian sin(Radian const& val) { /* Do Stuff */ }
Degree sin(Degree const& val) { /* Do Stuff */ }

As degrees and radians are different types you can't assign them to each other.

What you are trying to prevent is not preventable (developers with access to the internals still have access to the internals and as such can do anything). The way to stop this is not to give them access to the internals.

Make Val private and now they can not do any damage.

Martin York
+1  A: 

C++ way to do this is to make 'deg' and 'rad' private so the assignment you did would be very easily prevented by the compiler.

You then add assignment operators:

struct Radian
{
private:
  float rad;

public:
  Radian &operator=(const Radian &r)
  {
    rad = r.rad;
    return *this;
  } 
  Radian &operator=(const Degree &d)
  {
    rad = d.deg * DegToRadFactor;
    return *this;
  } 
};

struct Degree
{
private:
  float deg;

public:
  Degree &operator=(const Degree &d)
  {
    deg = r.deg;
    return *this;
  } 
  Degree &operator=(const Radian &r)
  {
    deg = r.rad / DegToRadFactor;
    return *this;
  } 
};

(This wont be compilable. You have to separate the assignment operator implementations because of the declaration orders. So this is just to show the principle)

rstevens
A: 

Lakos touches on this (amongst other things).

In his opinion (and I happen to agree with him) you shouldn't have degrees and radians tied to each other. If you need to convert from one to another then have a third class/struct to perform the switch.

So you would have 3 classes, Radians, Degrees and RadiansToDegrees, of which you could use RadiansToDegrees as a simple functor.

graham.reeds
What's wrong with a true function? After all, `RadiansToDegrees(Radians(50))` is simpeler then `RadiansToDegrees::singleton(Radians(50))`
MSalters
As a functor the signature would be RadiansToDegrees(50)(). Also where would you put that function? I like to have files named as the classes. If you wanted to go both ways you could have a file called ConvertDegreesRadians which could have Functors to go either way.
graham.reeds