• Advertisement
Sign in to follow this  

Best way of ensuring semantic type consistency?

This topic is 1151 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been thinking for a while on how to reduce bugs in the code I write.  One thing that I see a lot in other code is stuff like unsanitized strings or wrong units. Now, there are a lot of ways to resolve these, but I'm wondering which is best, at least in general.  For a single use case to go off of, and because some techniques work better than others for different things, we'll use the example of a timestamp, measured in seconds.

There are three ways off the top of my head I can think of to make timestamps "safer" in C++:

 

1.  Apps Hungarian

 

This is the most crude, "old-school" way, and basically looks like this:

int secTimestamp = 42;

It works, it's visible at a glance to the programmer that the timestamp is in seconds, but it's ugly and the compiler isn't going to give you any help.

 

2.  Typedefs

 

We can also use typedefs, like so:

typedef int Seconds;
Seconds timestamp = 42;

Also not typesafe, and less visible at a glance (usually), but also less baggage on the variable name.

 

3  Units

 

C++11 has support for user-defined literals, which can be typed to be a sort of "unit"

struct Seconds {int seconds; Seconds(int time) : seconds(time) {} };

constexpr Seconds operator "" _s(int time)
{
  return Seconds { time };
}

Seconds timestamp = 42_s;

This is at least typesafe, but the unit is kind of weird and it feels a bit... heavyweight, for the task.  It also only works under C++11.

What do you guys do?  Do you use one or more of these?  Do you do something completely different?  Nothing at all?

Edited by SeraphLance

Share this post


Link to post
Share on other sites
Advertisement

As far as time is concerned, you're typically dealing with either an absolute point in time, or a difference between two times. So the same way you have points and vectors in a mathematics library, I'd have Time and TimeDelta types in my time library to clearly communicate the semantic differences between the two. For instance, you wouldn't be able to add two Time's together, subtracting two Time's would result in a TimeDelta, etc. It may seem a little heavyweight at first, but it doesn't take terribly long to implement and will prevent a lot of common bugs when dealing with "absolute" versus "relative" measurements.

Share this post


Link to post
Share on other sites

As far as time is concerned, you're typically dealing with either an absolute point in time, or a difference between two times. So the same way you have points and vectors in a mathematics library, I'd have Time and TimeDelta types in my time library to clearly communicate the semantic differences between the two. For instance, you wouldn't be able to add two Time's together, subtracting two Time's would result in a TimeDelta, etc. It may seem a little heavyweight at first, but it doesn't take terribly long to implement and will prevent a lot of common bugs when dealing with "absolute" versus "relative" measurements.

 

I'm more referring to unit differences in this case.  That said, you bring up a good point.  I've seen "Dates" and "Durations" (or some variant of the term) used to resolve that pretty successfully, yeah.  Unless you're referring to times being fully unit aware in general, such as being able to call "TimeDelta.seconds", then that's another way to do it, but it's significantly more heavyweight.  You need a bunch of static factories for each measurement and such.  I would imagine something like this in Java (please forgive compiler errors):
 

class TimeDelta
{
  public int milliseconds;
  public int seconds;
  public int minutes;
  public int hours;

  private TimeDelta(int ms)
  {
    millisecnds = ms;
    seconds = ms/1000;
    minutes = ms/60000;
    hours   = ms/360000;
  }

  static TimeDelta FromMilliseconds(int milliseconds) { return new TimeDelta(milliseconds); }
  static TimeDelta FromSeconds(int seconds) { return new TimeDelta(seconds*1000); }
  static TimeDelta FromMinutes(int minutes) { return new TimeDelta(minutes*60000); }
  static TimeDelta FromHours(int hours) { return new TimeDelta(hours*360000); }
}
Edited by SeraphLance

Share this post


Link to post
Share on other sites
Clear naming conventions and unit tests is the common way, given that most languages have absolutely no way to solve this without heavy-weight objects, C++ included (well, in C++ it would be light-weight objects, zero-overhead on most ABIs when in optimized builds, but still).

Share this post


Link to post
Share on other sites

I'm more referring to unit differences in this case.  That said, you bring up a good point.  I've seen "Dates" and "Durations" (or some variant of the term) used to resolve that pretty successfully, yeah.  Unless you're referring to times being fully unit aware in general, such as being able to call "TimeDelta.seconds", then that's another way to do it, but it's significantly more heavyweight.  You need a bunch of static factories for each measurement and such.

As far as being unit-aware, you could probably just get away with providing a robust interface for working with multiple units. For instance, the type could store everything at the highest resolution you care about internally (milliseconds, microseconds, etc.), and any overloaded operators would operate in that unit, but expose an interface for operating in other units. Think TimeSpan and DateTime from .NET.

 

I'd say the hardest part is making sure all your engineers actually remember to use the special time types instead of raw integers biggrin.png

Edited by Zipster

Share this post


Link to post
Share on other sites

I use the user-defined literal approach (I refer to them as Concrete Types), but with more changes. Mainly, I have dimensional units - that is, 'time' would have the units be 0 for distance, 0 for mass, 1 for time.

 

You'd see something like this as the declaration:

 

template <typename T, uint dimLength, uint dimMass, uint dimTime, ...etc...> class concrete_type...

I also have more significant work involved within the concrete_type class to enforce type safety and make it more difficult to accidentally assign values that don't make sense.

 

The nice thing about it is that:

 

time val1 = 1_s;
length val2 = 1_m;

auto val3 = val2 / val1; // decltype(val3) == speed.

float val4 = val2 / val1; // error
time val5 = val2 / vall; // error
float val6 = val3; // error
float val6 = val3.raw(); // OK
Edited by Ameise

Share this post


Link to post
Share on other sites

 

I use this strong typedef implementation: http://www.gamedev.net/topic/663806-declare-custom-types-of-ints/#entry5198653

^That thread is about integer types, but it should work for floating point types too.

 

The reason I prefer concrete types is that they give me type safety on operations as well (length * length = area).

 

The problem is, this doesn't work with functions that the libray writer didn't think of. Your units library must be able to provide all the math functions you need. Otherwise you still have to cast back to int whenever you're calling a math function. + and * are easy, but what about pow? what about integration? If you're doing computation you often need math libraries, and you might even need a particular math library that doesn't support unit types.

 

It's easier with units of the same dimention, such as haveing unit classes for seconds, minutes, hours, but for when you need formula that multiply terms, the solution becomes much less eligant.

Share this post


Link to post
Share on other sites

 

 

I use this strong typedef implementation: http://www.gamedev.net/topic/663806-declare-custom-types-of-ints/#entry5198653

^That thread is about integer types, but it should work for floating point types too.

 

The reason I prefer concrete types is that they give me type safety on operations as well (length * length = area).

 

The problem is, this doesn't work with functions that the libray writer didn't think of. Your units library must be able to provide all the math functions you need. Otherwise you still have to cast back to int whenever you're calling a math function. + and * are easy, but what about pow? what about integration? If you're doing computation you often need math libraries, and you might even need a particular math library that doesn't support unit types.

 

It's easier with units of the same dimention, such as haveing unit classes for seconds, minutes, hours, but for when you need formula that multiply terms, the solution becomes much less eligant.

 

 

So long as you can derive the base type from your templates and get the raw value, you can write templated overrides for any math function. Particularly with C++11 ability to derive the return type.

This seems like less of a problem to me, and more of a "you'll have to do this, too".

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement