I’ve been trying to think of a way of declaring strongly typed typedefs, to catch a certain class of bugs in the compilation stage. It’s often the case that I’ll typedef an int into several types of ids, or a vector to position or velocity:
typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;
This can make the intent of code more clear, but after a long night of coding one might make silly mistakes like comparing different kinds of ids, or adding a position to a velocity perhaps.
EntityID eID;
ModelID mID;
if ( eID == mID ) // <- Compiler sees nothing wrong
{ /*bug*/ }
Position p;
Velocity v;
Position newP = p + v; // bug, meant p + v*s but compiler sees nothing wrong
Unfortunately, suggestions I’ve found for strongly typed typedefs include using boost, which at least for me isn’t a possibility (I do have c++11 at least). So after a bit of thinking, I came upon this idea, and wanted to run it by someone.
First, you declare the base type as a template. The template parameter isn’t used for anything in the definition, however:
template < typename T >
class IDType
{
unsigned int m_id;
public:
IDType( unsigned int const& i_id ): m_id {i_id} {};
friend bool operator==<T>( IDType<T> const& i_lhs, IDType<T> const& i_rhs );
};
Friend functions actually need to be forward declared before the class definition, which requires a forward declaration of the template class.
We then define all the members for the base type, just remembering that it’s a template class.
Finally, when we want to use it, we typedef it as:
class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;
The types are now entirely separate. Functions that take an EntityID will throw a compiler error if you try to feed them a ModelID instead, for example. Aside from having to declare the base types as templates, with the issues that entails, it’s also fairly compact.
I was hoping anyone had comments or critiques about this idea?
One issue that came to mind while writing this, in the case of positions and velocities for example, would be that I can’t convert between types as freely as before. Where before multiplying a vector by a scalar would give another vector, so I could do:
typedef float Time;
typedef Vector3 Position;
typedef Vector3 Velocity;
Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };
Position newP = p + v*t;
With my strongly typed typedef I’d have to tell the compiler that multypling a Velocity by a Time results in a Position.
class TimeT;
typedef Float<TimeT> Time;
class PositionT;
typedef Vector3<PositionT> Position;
class VelocityT;
typedef Vector3<VelocityT> Velocity;
Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };
Position newP = p + v*t; // Compiler error
To solve this, I think I’d have to specialize every conversion explicitly, which can be kind of a bother. On the other hand, this limitation can help prevent other kinds of errors (say, multiplying a Velocity by a Distance, perhaps, which wouldn’t make sense in this domain). So I’m torn, and wondering if people have any opinions on my original issue, or my approach to solving it.
2
These are phantom type parameters, that is, parameters of a parameterised type that are used not for their representation, but to separate different “spaces” of types with the same representation.
And speaking of spaces, that’s a useful application of phantom types:
template<typename Space>
struct Point { double x, y; };
struct WorldSpace;
struct ScreenSpace;
// Conversions between coordinate spaces are explicit.
Point<ScreenSpace> project(Point<WorldSpace> p, const Camera& c) { … }
As you’ve seen, though, there are some difficulties with unit types. One thing you can do is decompose units into a vector of integer exponents on the fundamental components:
template<typename T, int Meters, int Seconds>
struct Unit {
Unit(const T& value) : value(value) {}
T value;
};
template<typename T, int MA, int MB, int SA, int SB>
Unit<T, MA - MB, SA - SB>
operator/(const Unit<T, MA, SA>& a, const Unit<T, MB, SB>& b) {
return a.value / b.value;
}
Unit<double, 0, 0> one(1);
Unit<double, 1, 0> one_meter(1);
Unit<double, 0, 1> one_second(1);
// Unit<double, 1, -1>
auto one_meter_per_second = one_meter / one_second;
Here we’re using phantom values to tag runtime values with compile-time information about the exponents on the units involved. This scales better than making separate structures for velocities, distances, and so on, and might be enough to cover your use case.
2
I had a similar case where I wanted to distinguish different meanings of some integer values, and forbid implicit conversions between them. I wrote a generic class like this:
template <typename T, typename Meaning>
struct Explicit
{
//! Default constructor does not initialize the value.
Explicit()
{ }
//! Construction from a fundamental value.
Explicit(T value)
: value(value)
{ }
//! Implicit conversion back to the fundamental data type.
inline operator T () const { return value; }
//! The actual fundamental value.
T value;
};
Of course if you want to be even more safe, you can make the T
constructor explicit
as well. The Meaning
is then used like this:
typedef Explicit<int, struct EntityIDTag> EntityID;
typedef Explicit<int, struct ModelIDTag> ModelID;
4
I’m not sure how the following works out in production code (I’m a C++/programming beginner, like, CS101 beginner), but I cooked this up using C++’s macro sys.
#define newtype(type_, type_alias) struct type_alias {
/* make a new struct type with one value field
of a specified type (could be another struct with appropriate `=` operator*/
type_ inner_public_field_thing; // the masked_value
explicit type_alias( type_ new_value ) { // the casting through a constructor
// not sure how this'll work when casting non-const values
// (like `type_alias(variable)` as opposed to `type_alias(bare_value)`
inner_public_field_thing = new_value; } }
2