A Proposal to Add Strong Type Aliases to the Standard Language

Started by
28 comments, last by Matt-D 11 years, 2 months ago
So I still dont understand why simply using a class is not an acceptable solution. You want a new type with your own defined data and behaviors... which is exactly what classes are meant for.

The point is that we don't want to have to write identical classes if we can help it.

Here's another example:

struct Vector3D {
  double x, y, z;
  
  Vector3D(double x, double y, double z) : x(x), y(y), z(z) {
  }
  
  void print(std::ostream &os) const {
    os << '(' << x << ',' << y << ',' << z << ')';
  }
};

struct Point3D {
  double x, y, z;
  
  Point3D(double x, double y, double z) : x(x), y(y), z(z) {
  }
  
  void print(std::ostream &os) const {
    os << '(' << x << ',' << y << ',' << z << ')';
  }
};

It would be nice to say "a Point3D works exactly as a Vector3D". A typedef would allow that, but then you won't actually get two separate types.

So, like this?


class UnitOfLength
{
public:
	// Add your constructors and overloaded operators here

private:
	float mValue;
};

class Inch : public UnitOfLength {};
class Centimeter : public UnitOfLength {};

int main(void)
{
	Inch i;
	Centimeter c = i;	// <--- compile error

	return 0;
}
Advertisement

The constructors don't get exposed when you do that, so you'll have to rewrite them for Inch and Centimeter. Also, you can't do that with primitive types.

Of course there are alternative ways to do this, but having this feature in the language would make certain things easier, that's all.

The constructors don't get exposed when you do that, so you'll have to rewrite them for Inch and Centimeter. Also, you can't do that with primitive types.

Of course there are alternative ways to do this, but having this feature in the language would make certain things easier, that's all.

Ahh, that's right, I forgot that the constructors and operators wont be inherited and you'll need to declare them in the child classes.

Ok, I can see where the motivation for this comes from, but on the other hand I've never seen a real case where I'd save enough typing/code duplication to make me want this. In the Inch/Centimeter example, it's only 2 classes. But even if more, this is stuff that you write once and then it'll sit in a lib untouched afterwards. And, with a class you do get the additional abilities like operator overloading, and controlling which ones to allow and which to not allow.

In the code sample you gave... it just seems out of control. You really just need a single 2D Point object (you have 13!), but somehow it looks like you're trying to prevent your high-level code from doing the wrong thing by moving your error checking to the lowest levels, and in doing so actually making more work for yourself with all these different typedefs that all cant be automatically cast to each other. If I saw this code anyplace I worked I'd seriously have to wonder what the heck was going on.

[quote name='0r0d' timestamp='1356845707' post='5015653']
In the code sample you gave... it just seems out of control. You really just need a single 2D Point object (you have 13!), but somehow it looks like you're trying to prevent your high-level code from doing the wrong thing by moving your error checking to the lowest levels, and in doing so actually making more work for yourself with all these different typedefs that all cant be automatically cast to each other. If I saw this code anyplace I worked I'd seriously have to wonder what the heck was going on.
[/quote]

I guess that paragraph was responding to SOTL, not me. I'll reply anyway. :)

I have to admit that those 13 classes seem a little overwhelming, but I don't see his code as moving error checking to the lowest levels. It is simply making the type system work for him, by forbidding operations that are most likely bugs. I can see myself doing something similar for vertices in a 3D pipeline: If you add model coordinates and world coordinates together, you probably have a bug, and having separate types for them makes total sense.

At work we deal a lot with orders to buy or sell stocks. Whenever I use a double in that context, I actually know more about it than that: It's an order size in shares, or a price in dollars/share, or a price in euros/share, or a dollar value for the order (or sometimes more exotic things, but let's keep it simple). It would make total sense to use separate types for these things, although in the end they are all just doubles when you get down to assembly level. I just would like my compiler to bomb if I ever try to add a quantity and a price.

Ahh, that's right, I forgot that the constructors and operators wont be inherited and you'll need to declare them in the child classes.
Though see n2141 and forwarding constructors in C++11.
Also, you can't do that with primitive types.

You could always write a "boxed type" template class that encapsulates a primitive type, while supporting the same operators the primitive type does. So, essentially reinventing Java's "boxed types" in C++ using templates. I'm not in a position to say whether that would be worth the effort, though. My guess would be that it isn't.
Well, it's a common enough situation that there's a boost library for simplifying the creation of new arithmetic types.
You could always write a "boxed type" template class that encapsulates a primitive type, while supporting the same operators the primitive type does. So, essentially reinventing Java's "boxed types" in C++ using templates. I'm not in a position to say whether that would be worth the effort, though. My guess would be that it isn't.
I have one of those in my engine, which acts like the type (operator wise), but with an explicit constructor from that type.
e.g. short version:
template<class T, class Tag> struct P
{
	P() : value() {}
	explicit P( T v ) : value(v) {}
	T value;
};
struct MetreTag {};
struct InchTag {};
typedef P<float, MetreTag> Metre;
typedef P<float, InchTag> Inch;
And then I write conversion functions like:
Inch ToInches(Metre m) { return Inch(m.value * 39.3700787f); }
void test()
{
	Metre m(10);
	Inch i( ToInches(m) );
	Inch error( m );//won't compile
}
I haven't used the boost solution, but I imagine it's similar to this, so I probably suffer from the same issues that SOTL found with boost's version.

Your version looks great for basic types (which mine doesn't support), but doesn't support classes (which mine does).

Most of my strong aliases have to do with a basic 'Point' type.

I could write a template just for points, but I figured in such a strongly-typed language as C++, why not have a generalized solution built into the language?

We did so for enums with the C++11 'enum class'.

Update: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3515.pdf

This topic is closed to new replies.

Advertisement