views:

716

answers:

10

I often find myself using Integers to represent values in different "spaces". For example...

int arrayIndex;
int usersAge;
int daysToChristmas;

Ideally, I'd like to have separate classes for each of these types "Index","Years" and "Days", which should prevent me accidentally mixing them up. Typedefs are a help from a documnentation perspective, but aren't type-safe enough.

I've tried wrapper classes, but end up with too much boilerplate for my liking. Is there a straightforward template-based solution, or maybe something ready-to-go in Boost?

EDIT: Several people have talked about bounds-checking in their answers. That maybe a handy side-effect, but is NOT a key requirement. In particular, I don't just want to prevent out-of-bound assignments, but assignments between "inappropriate" types.

+6  A: 

One funky "hack" you could use is a template non-type parameter to create wrapper types. This doesn't add any bounds but it does allow to treat them as different types with only one set of boilerplate template code. I.e.

template<unsigned i>
class t_integer_wrapper
  {
  private:
    int m_value;
  public:
     // Constructors, accessors, operators, etc.
  };

typedef t_integer_wrapper<1> ArrayIndex;
typedef t_integer_wrapper<2> UsersAge;

Extend the template with lower and upper bounds or other validation as you like. Not pretty by a long shot though.

MadKeithV
Interesting solution. Would make me feel dirty. But using C++ always makes me feel dirty anyway, so +1.
OregonGhost
Doesn't this fit into the "boilerplate code" category Roddy was talking about?
OJ
You only have to write this boilerplate once, instead of multiple times to get distinct C++ types - variation in the non-type parameter changes the type of the template instantiation.
MadKeithV
neat solution. I'd try to throw in __LINE__/__FILE__ somehow to make it simpler to select the unique template version for the typedef.
Greg Rogers
+4  A: 

I remember solving a similar problem with a simple template where you would specify the allowed range, i.e.

Int<0, 365> daysToChristmas;
Int<0, 150> usersAge;
Int<0, 6> dayOfWeek;

You get the point. Now you could just derive from such a template type, like

class DayOfYear: public Int<0, 365> {}

and you could no longer pass a user age to a function expecting a DayOfYear, and you wouldn't have to use the angled brackets.

OregonGhost
+1  A: 

Adding in the operator int () will allow you to use the object where a normal int is required. You can also add in a operator = () to set it within range.

class DayType 
  {
  public:
    static int const low = 1;
    static int const high = 365;
  };

template<class TYPE>
class Int
  {
  private:
    int m_value;
  public:
     operator int () { return m_value; }
     operator = ( int i ) { /* check and set*/ }
  };

  Int<DayType> day;
  int d = day;
  day = 23;

I hope this helps.

David Allan Finch
That's a good addition to my proposal, though I thought it would be obvious to add those - it might be wise to make the operator int() explicit anyway. But it's fine this way too.
OregonGhost
True, but it can be handy to use them as real built in types but it might be wise to stop the auto conversion from type to type. Not sure if you can put and explicit on a operator - naff if you can't ;)
David Allan Finch
Not sure. It's just C++ does scary things with auto conversion regarding overloaded functions or pointer/int conversions. For a string type, for example, it seems to be good practice not to have implicit conversion because of these problems. You know, the things you can only do in C++.
OregonGhost
I wouldn't do this without seriously testing it first - implicit conversions are going to bite you in the ass in surprising ways.
MadKeithV
A: 
int arrayIndex;

This is what std::size_t is for.

int usersAge;

People can't have negative ages and it is not useful/easy to set a fixed upper bound for ages. So here you should just use unsigned int.

int daysToChristmas;

Days to Christmas requires special attention. The number of days until Christamas can range from 0-366. The simple solution is to write the following wherever needed:

assert( 0 < daysToChristmas && daysToChristmas < 366 )

If you feel you're going to duplicate that assert in too many places, then David Allan Finch proposes a neat solution for this case. Though I am partial to using the assert.

fpsgamer
Ummm, you're missing the point. I can still say "arrayIndex = usersAge;" without any warning from the compiler that I'm fouling up. I'm looking for a strongly typed solution.
Roddy
I just don't think the examples you gave warrant a non-trivial solution. There is only so much you can do to prevent undesired assignment, for example a template solution allows assignment between any two numbers which share the same domain. So daysToChristmas can be assigned to daysToBirtday.
fpsgamer
ok - maybe I need better examples!
Roddy
+4  A: 

This is a generic "StrongType" template that we use to wrap different types and contexts. The only significant difference to this answer is that we prefer to use a tag-type that gives a meaningful name to each specialized wrapper type:

template <typename ValueType, class Tag> class StrongType {
public:
  inline StrongType() : m_value(){}
  inline explicit StrongType(ValueType const &val) : m_value(val) {}
  inline operator ValueType () const {return m_value; }
  inline StrongType & operator=(StrongType const &newVal) {
    m_value = newVal.m_value;
    return *this;
  }
private:
  //
  // data
  ValueType m_value;
};

And a use of the template as follows:

class ArrayIndexTag;
typedef StringType<int, ArrayIndexTag> StrongArrayIndex;
StringArrayIndex arrayIndex;

Notice too, that all of the functions are 'inline', the intention being that the compiler can do its best to generate exactly the same code it would generated had the template not been used at all!

Richard Corden
David Allan Finch
All functions defined in a class definition (including a class template) are implicitly inline. Of course inline is only a hint anyway, so there's no way to externally test whether a compiler always treats them the same whether they're marked inline or not, so there's no harm in spelling it out.
Steve Jessop
+6  A: 

Boost does in fact have a library specifically for this type of thing! Check out the Boost.Units library.

Ryan Fox
A: 

For an array index I'd use size_t provided I didn't need negative values, because that's what it's there for. Of course that frequently is unsigned int, so won't give you any type safety at all. However, anything that did give you type safety (i.e. that stopped you assigning an unsigned int to an array index) would also stop you returning a size_t value into your type. That might be too much type safety anyway.

You could probably use an enum for bounded ranges:

enum YearDay {
    FirstJan = 0,
    LastDecInLeapYear = 365
};

You can assign YearDay to int, but you can't assign an int (or another enum type) to YearDay without an explicit cast. Any value between the least and greatest named value in an enum is a valid value for the enum. Assigning a value outside the range [0,365] results in undefined behaviour. Or possibly an unspecified or implementation-defined result, I can't remember.

Age is tricky, because it's almost bounded, but not quite. You could use 969 (age of Methuselah) in an enum, or a class wrapping an int with explicit conversions as described by others.

Steve Jessop
humm. Wouldn't LastDecInLeapYear be 366?
EvilTeach
Well, I'm counting from 1stJan = 0. 366 days in the year, so last one is 365. It might be more sensible to count from 1stJan = 1, mind you, but I copied the meaning of tm_yday. And YearDay isn't the same thing as daystoXmas anyway, just a similar example, so the base doesn't affect the questioner.
Steve Jessop
+2  A: 

In addition to the Boost Units library mentioned by Ryan Fox, there will be also the Boost Constrained Value library, which is currently under review.

Who knows when or if it will hit an official Boost release, but you can probably try it out anyway.

Alastair
+4  A: 

You could try BOOST_STRONG_TYPEDEF. From boost/strong_typedef.hpp:

// macro used to implement a strong typedef.  strong typedef
// guarentees that two types are distinguised even though the
// share the same underlying implementation.  typedef does not create
// a new type.  BOOST_STRONG_TYPEDEF(T, D) creates a new type named D
// that operates as a type T.
Ferruccio
That looks very much like the answer I need. Boost::units isn't supported in my version of boost, but this is.
Roddy
A: 

Check out this old CUJ article on this subject. IIRC the technique desribes how to make it work with all the fundamental operators

dcw