Explicit typedef

J

jlongstreet

Please correct any misconceptions, or voice concerns about this being
a stupid idea in general.

I was wondering today: why doesn't C++ have explicit typedefs?

explicit typedef unsigned int ScreenXCoord;
explicit typedef unsigned int ScreenYCoord;
explicit typedef unsigned int WindowXCoord;
explicit typedef unsigned int WindowYCoord;

ScreenXCoord sx1, sx2;
ScreenYCoord sy1, sy2;
WindowXCoord wx1, wx2;
WindowYCoord wy1, wy2;
....
sx1 = sx2; // ok
sy1 = sy2; // ok
wx1 = wx2; // ok
wy1 = wy2; // ok

sx1 = wy1; // error, converting WindowYCoord to ScreenXCoord

This way, ScreenXCoord != ScreenYCoord != WindowXCoord !=
WindowYCoord.

Many times, it can be unclear what sort of thing a particular value
refers to, even knowing its type, especially with integers, which
could mean width, height, count of bytes, inches, days in a payroll
period, or just about anything else. This is why Hungarian notation
was invented -- cbCount is count of bytes, wxWidth is width in window
coordinates, etc.

Obviously, typedefs are one part of the solution -- it's obvious what
a variable declared as ScreenXCoord is for. But there's no stopping
the programmer from assigning a non-typedef'd unsigned int or another
typedef of unsigned int to that variable.

It seems to me the solution would be to add support for the explicit
keyword to typedef. It would be something like enum in C++,
convertible from their underlying type in some cases, but not the
others.

Once again, please post any criticism (or praise, if I really haven't
overlooked anything).
 
S

Salt_Peter

Please correct any misconceptions, or voice concerns about this being
a stupid idea in general.

I was wondering today: why doesn't C++ have explicit typedefs?

explicit typedef unsigned int ScreenXCoord;
explicit typedef unsigned int ScreenYCoord;
explicit typedef unsigned int WindowXCoord;
explicit typedef unsigned int WindowYCoord;

ScreenXCoord sx1, sx2;
ScreenYCoord sy1, sy2;
WindowXCoord wx1, wx2;
WindowYCoord wy1, wy2;
...
sx1 = sx2; // ok
sy1 = sy2; // ok
wx1 = wx2; // ok
wy1 = wy2; // ok

sx1 = wy1; // error, converting WindowYCoord to ScreenXCoord

This way, ScreenXCoord != ScreenYCoord != WindowXCoord !=
WindowYCoord.

Many times, it can be unclear what sort of thing a particular value
refers to, even knowing its type, especially with integers, which
could mean width, height, count of bytes, inches, days in a payroll
period, or just about anything else. This is why Hungarian notation
was invented -- cbCount is count of bytes, wxWidth is width in window
coordinates, etc.

Obviously, typedefs are one part of the solution -- it's obvious what
a variable declared as ScreenXCoord is for. But there's no stopping
the programmer from assigning a non-typedef'd unsigned int or another
typedef of unsigned int to that variable.

It seems to me the solution would be to add support for the explicit
keyword to typedef. It would be something like enum in C++,
convertible from their underlying type in some cases, but not the
others.

Once again, please post any criticism (or praise, if I really haven't
overlooked anything).

C++ has classes, and in this case, what *are) you dealing with?
axis values or coordinates?
Think encapsulation.

Generate a coordinate point, move it to a specific location.
Generate a Screen Coord there, move the original point again, generate
a Window coordinate there.
A window coordinate can be created from a screen coordinate but not
vice-versa.

#include <iostream>

template< typename T = unsigned >
class Coord
{
T x;
T y;
public:
// def parametized ctor
Coord(const T x_ = T(), const T y_ = T())
: x(x_), y(y_) { }
// compiler defined copy ctor will do
// virtual dtor required if you plan to use Coord* ptrs
T getX() const { return x; }
T getY() const { return y; }
void move(const T x_, const T y_)
{
x += x_;
y += y_;
}
protected:
void set(const T x_, const T y_)
{
x = x_;
y = y_;
}
friend std::eek:stream&
operator<<(std::eek:stream& os, const Coord& r_c)
{
os << "x = " << r_c.x;
os << "\ty = " << r_c.y;
return os;
}
};

template< typename T = unsigned >
class ScreenCoord : public Coord< T >
{
public:
explicit ScreenCoord(const Coord< T >& c)
: Coord< T >(c) { }
};

template< typename T = unsigned >
class WindowCoord : public Coord< T >
{
public:
explicit WindowCoord(const Coord< T >& c)
: Coord< T >(c) { }
// conversion op=
WindowCoord& operator=(const ScreenCoord< T > rhv)
{
set(rhv.getX(), rhv.getY());
return *this;
}
};

typedef Coord< > u_coord;
typedef ScreenCoord< > u_screencoord;
typedef WindowCoord< > u_windowcoord;

int main()
{
u_coord point;
std::cout << "point: " << point << std::endl;

std::cout << "point.move(+10, +20)\n";
point.move(+10, +20);

u_screencoord sc( point );
std::cout << "screencoord: " << sc << std::endl;

std::cout << "point.move(+20, +5)\n";
point.move(+20, +5);

u_windowcoord wc( point );
std::cout << "windowcoord: " << wc << std::endl;

std::cout << "\nwindowcoord = screencoord\n";
wc = sc; // ok
std::cout << wc << std::endl;

// sc = wc; // error, no conv op= available
}

/*
point: x = 0 y = 0
point.move(+10, +20)
screencoord: x = 10 y = 20
point.move(+20, +5)
windowcoord: x = 30 y = 25

windowcoord = screencoord
x = 10 y = 20
*/

___
And if one day a client codes the following mistake:

u_coord pt;
u_coord another(pt.getY(), pt.getY());

suggest the natural alternative - copy ctor:

u_coors pt;
u_coord( pt );

And now you have a system, not just a few typedefs that don't mean
anthing.
 
?

=?iso-8859-1?q?Erik_Wikstr=F6m?=

Not stupid but it's called a class !

You can use templates to do what you're doing.

I'm not sure I completely agree, one of the most common usages for
such a typedef would probably be to create new types based on the
built-in types, and while it's possible to create a class that
perfectly mimics an int, it would be (relatively) very much work in
proportion to the benefits. And as soon as I wanted a second kind of
type, also behaving like an int I'd have to copy the whole class.

Using templates you can get away with using just one class definition,
but you still need that one definition. And there is always the risk
that the compiler won't be able to optimize away the wrapping (perhaps
not a big risk but anyway). I've come up with the following, perhaps
someone have a better solution?

template<int N>
class MyInt{
int data;
public:
MyInt(int i) : data(i) { }
MyInt(const MyInt<N>& i) : data(i.data) {}
MyInt<N>& operator=(const MyInt<N>& i) { data = i.data; return
*this; }
/*operations etc*/
};

int main()
{
typedef MyInt<1> length;
typedef MyInt<2> volume;

length x = 5;
volume y = 1;
y = x; // Fail
}

I'm not saying it could not be done, but it would be much easier to
let the compiler understand that two types defined by typedefs are not
compatible.
 
P

peter koch

I'm not sure I completely agree, one of the most common usages for
such a typedef would probably be to create new types based on the
built-in types, and while it's possible to create a class that
perfectly mimics an int, it would be (relatively) very much work in
proportion to the benefits. And as soon as I wanted a second kind of
type, also behaving like an int I'd have to copy the whole class.

Using templates you can get away with using just one class definition,
but you still need that one definition. And there is always the risk
that the compiler won't be able to optimize away the wrapping (perhaps
not a big risk but anyway).
That doesn't matter that much. Firstly because all modern compilers (I
know of) will remove the wrapping, and secondly because you could
simply
I've come up with the following, perhaps
someone have a better solution?

template<int N>
class MyInt{
int data;
public:
MyInt(int i) : data(i) { }
MyInt(const MyInt<N>& i) : data(i.data) {}
MyInt<N>& operator=(const MyInt<N>& i) { data = i.data; return
*this; }
/*operations etc*/

};

int main()
{
typedef MyInt<1> length;
typedef MyInt<2> volume;

length x = 5;
volume y = 1;
y = x; // Fail

But you would probably want to allow y = x*x*x; (disregarding for the
moment the wisdom of using int to store weights and lengths).
This is exactly what the solution linked to by Gianni does and why an
"explicit typedef" is an insuffucient solution.

/Peter
 
?

=?iso-8859-1?q?Erik_Wikstr=F6m?=

That doesn't matter that much. Firstly because all modern compilers (I
know of) will remove the wrapping, and secondly because you could
simply






But you would probably want to allow y = x*x*x; (disregarding for the
moment the wisdom of using int to store weights and lengths).
This is exactly what the solution linked to by Gianni does and why an
"explicit typedef" is an insuffucient solution.

Yes, it was a bad example, since those represents different kinds of
units that should be convertible, however consider the case where you
store coordinates, you need X, Y and Z, and they are just numbers, you
can't really perform arithmetic operations on coordinates but nor can
you use an X coordinate on the Y-axis. There are a number of cases
where you want a simple type but want to make sure that the users
don't confuse which to use where a class/template solution is simply
overkill.
 
S

Sylvester Hesp

[sidenote: why is it that every time I quote a post from you, Erik, my news
app (Outlook Express) doesn't prefix your lines with a '>'? :)]

Erik Wikström said:
Using templates you can get away with using just one class definition,
but you still need that one definition.

But that definition is a one-time process only, and you can use it for any
type there-on. Of course, it is tedious to define all the possible
operators, but it is do-able as they're number is finite. However, we may
need the explicit conversion operator feature (N1592) that is proposed for
C++09, because you want your types to be convertible back to the primitives,
but not implicitely, and there's currently no way of doing that.
I've come up with the following, perhaps
someone have a better solution?

template<int N>
class MyInt{
int data;
public:
MyInt(int i) : data(i) { }
MyInt(const MyInt<N>& i) : data(i.data) {}
MyInt<N>& operator=(const MyInt<N>& i) { data = i.data; return
*this; }
/*operations etc*/

I would make MyInt(int) explicit (as, after all, that is what is needed),
and I would make the template parameter a typename rather than an int.
Assigning unique integers for every MyInt variant is pretty cumbersome in
large applications, you have to keep a list of numbers that are already
used. Instead, if you use a typename, everyone can define it's own tag to
instantiate a new MyInt variant, like this:

typedef MyInt<struct temperature_tag> temperature;
typedef MyInt<struct angle_tag> angle;

- Sylvester Hesp
 
?

=?iso-8859-1?q?Erik_Wikstr=F6m?=

[sidenote: why is it that every time I quote a post from you, Erik, my news
app (Outlook Express) doesn't prefix your lines with a '>'? :)]


Using templates you can get away with using just one class definition,
but you still need that one definition.

But that definition is a one-time process only, and you can use it for any
type there-on. Of course, it is tedious to define all the possible
operators, but it is do-able as they're number is finite. However, we may
need the explicit conversion operator feature (N1592) that is proposed for
C++09, because you want your types to be convertible back to the primitives,
but not implicitely, and there's currently no way of doing that.

There was a proposal to add opaque typedefs in n1891, where two types
were used, one which could be converted to the base-type and one that
could not. However there seems to be little progress on getting opaque
typedefs into the standard (even though it has been on the EWG issue
list since 2003 and there have been explicit requests for such a
feature).
 
J

James Longstreet

I'm not sure I completely agree, one of the most common usages for
such a typedef would probably be to create new types based on the
built-in types, and while it's possible to create a class that
perfectly mimics an int, it would be (relatively) very much work in
proportion to the benefits. And as soon as I wanted a second kind of
type, also behaving like an int I'd have to copy the whole class.

Using templates you can get away with using just one class definition,
but you still need that one definition. And there is always the risk
that the compiler won't be able to optimize away the wrapping (perhaps
not a big risk but anyway). I've come up with the following, perhaps
someone have a better solution?

This is what I was getting at -- why create a class when a typedef is
*really* what you want?
template<int N>
class MyInt{
int data;
public:
MyInt(int i) : data(i) { }
MyInt(const MyInt<N>& i) : data(i.data) {}
MyInt<N>& operator=(const MyInt<N>& i) { data = i.data; return
*this; }
/*operations etc*/

};

int main()
{
typedef MyInt<1> length;
typedef MyInt<2> volume;

length x = 5;
volume y = 1;
y = x; // Fail

}

I'm not saying it could not be done, but it would be much easier to
let the compiler understand that two types defined by typedefs are not
compatible.

Exactly... besides, MyInt<1> really does not express what you mean,
and you risk bypassing the entire concept when someone forgets that
MyInt<47> was already typedef'd to something else.

Well, at least I'm not totally stupid. Now if I could only figure out
this blasted bug I'm *supposed* to be fixing :)

- James
 
G

Gianni Mariani

James said:
This is what I was getting at -- why create a class when a typedef is
*really* what you want?

What's all the phobia about classes for ?

Classes are one of the fundamental parts of the lanaguage, avoiding
their use is like avoiding the whole point of the language. (not
literally speaking - in a more meraphorical emotional kind of way - you
know.).

The point it, there is a way to do what you want, it's first class (no
pun intended - but funny anyway) supported by the language so use the
darn thing.
 
P

peter koch

Yes, it was a bad example, since those represents different kinds of
units that should be convertible, however consider the case where you
store coordinates, you need X, Y and Z, and they are just numbers, you
can't really perform arithmetic operations on coordinates but nor can
you use an X coordinate on the Y-axis. There are a number of cases
where you want a simple type but want to make sure that the users
don't confuse which to use where a class/template solution is simply
overkill.
That is solved by handling stuff in appropriate quantities. If
something needs a coordinate, provide it as a coordinate and not as
two or three independent quantities. This also solves the problem you
mention with "not doing arithmetic operations".

/Peter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,779
Messages
2,569,606
Members
45,239
Latest member
Alex Young

Latest Threads

Top