To Typedef or not to Typedef

Jun 4, 2009 at 8:20pm
That is the question... I have yet to find any resource that can convince me of the value of typedef and yet its crawling around everywhere in C++ programs. I know it's carry over from C programmers that just can't shake it, but is there really any place for it in pure C++. How might I benefit from using typedef? I'm working quite a bit in OpenGL of late and these typedefs are making it considerably difficult to fully grasp the API. While its not necessary for me to understand it fully, I like to know whats under the hood. Here's some examples from GL.h and GLU.h that piss me off.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
typedef unsigned int GLenum;
typedef unsigned char GLboolean;
typedef unsigned int GLbitfield;
typedef signed char GLbyte;
typedef short GLshort;
typedef int GLint;
typedef int GLsizei;
typedef unsigned char GLubyte;
typedef unsigned short GLushort;
typedef unsigned int GLuint;
typedef float GLfloat;
typedef float GLclampf;
typedef double GLdouble;
typedef double GLclampd;
typedef void GLvoid;

typedef void (CALLBACK* GLUtessBeginProc)        (GLenum);
typedef void (CALLBACK* GLUtessEdgeFlagProc)     (GLboolean);
typedef void (CALLBACK* GLUtessVertexProc)       (void *);
typedef void (CALLBACK* GLUtessEndProc)          (void);
typedef void (CALLBACK* GLUtessErrorProc)        (GLenum);
typedef void (CALLBACK* GLUtessCombineProc)      (GLdouble[3],
                                                  void*[4], 
                                                  GLfloat[4],
                                                  void** );
typedef void (CALLBACK* GLUtessBeginDataProc)    (GLenum, void *);
typedef void (CALLBACK* GLUtessEdgeFlagDataProc) (GLboolean, void *);
typedef void (CALLBACK* GLUtessVertexDataProc)   (void *, void *);
typedef void (CALLBACK* GLUtessEndDataProc)      (void *);
typedef void (CALLBACK* GLUtessErrorDataProc)    (GLenum, void *);
typedef void (CALLBACK* GLUtessCombineDataProc)  (GLdouble[3],
                                                  void*[4], 
                                                  GLfloat[4],
                                                  void**,
                                                  void* );

/* backwards compatibility: */
typedef class GLUnurbs GLUnurbsObj;
typedef class GLUquadric GLUquadricObj;
typedef class GLUtesselator GLUtesselatorObj;
typedef class GLUtesselator GLUtriangulatorObj;

#else

typedef struct GLUnurbs GLUnurbs;
typedef struct GLUquadric GLUquadric;
typedef struct GLUtesselator GLUtesselator;

/* backwards compatibility: */
typedef struct GLUnurbs GLUnurbsObj;
typedef struct GLUquadric GLUquadricObj;
typedef struct GLUtesselator GLUtesselatorObj;
typedef struct GLUtesselator GLUtriangulatorObj;
Jun 4, 2009 at 8:51pm
Most of the uses above just obfuscate the code, as you've already noticed. typedefing a function pointer is common, because the function pointer syntax is ugly and unreadable in itself.

Without typedefs, generic programming would be virtually impossible. Look at the STL. Look at the boost libraries.
Jun 4, 2009 at 9:10pm
Well, looks like someone likes to declare their own keywords. I've heard of someone who liked to #define whilst while . Seems they liked to talk posh.

Aside from function pointers, this is another legitimate use of typedef ("CI" stands for "case insensitive, by the way):
typedef std::map<std::string,Variable *,strcmpCI> variablesMap_t;
Last edited on Jun 4, 2009 at 9:10pm
Jun 4, 2009 at 9:20pm
Typedefs for basic types allow the type to be retyped on platforms where the built-in type may differ. IE, if OpenGL expects 'int' to be 32-bits wide, then GLint might be typedefed as 'long' on X platform instead of 'int' if 'int' is only 16-bits wide. This aids in portability and helps prevent potential compiler issues where types may differ ever so subtley.

As for things like GLfloat/GLdouble/GLvoid where the type likely won't change -- it's probably just to maintain consistency.


Typedefs for function pointers greatly ease usage. Function pointers are a mess unless you typedef them.

Typedefing structs like that example is a goofy C thing that doesn't happen much in C++.


In short:

Typedefs can be easily changed. Using built-in types everywhere makes things hard to change. By typedefing everything instead of using built-in types you make the program more adaptable to different environments.
Jun 5, 2009 at 12:22pm
The counterargument though is that C++ wants to be strongly typed but typedefs are the antithesis to that.
A GLubyte can be assigned to a GLboolean and vice versa without complaint because they are the same underlying type (unsigned char).

A better approach is to make them real types.

1
2
3
4
5
6
7
8
struct GLubyte {
    typedef unsigned char value_type;
    explicit GLubyte( value_type c ) : value( c ) {}
    value_type get() const { return value; }
    // operators here (use boost::operators!)
  private:
    value_type value;
};



Jun 5, 2009 at 1:41pm
Are you sure that's a C++ header file? Me thinks it's C.

Why? The treatment of struct. In C, struct occupies it's own namespace. So you need to say struct stat for example. In C++, that is not the case. So using a typedef on structs is helpful.

Although typedef creates synonyms, it can be useful. Consider the case where you need a 32 bit unsigned number. Using a name uint32 is more portable than using the native type directly and the intent is clear.

The presented list of typedefs is excessive and perhaps misleading in cases. For example, GLboolean being defined as an unsigned char will introduce inefficiencies as C performs logic on ints.
Jun 5, 2009 at 1:52pm
A better approach is to make them real types.


The problem with that is:

1) It's a lot of extra code (doing that for each type? blech) Although I suppose you could template it.
2) It's not C friendly (remember openGL is a C lib as well, not just a C++ lib). Although I suppose you could #ifdef that
3) It makes basic types objects instead of basic types -- making them subject to additional compiler mingling, such as additional padding (sizeof(jsmith::GLubyte) != sizeof(opengl::GLubyte).. .which is quite a big deal when you consider these types are going to be used in packed arrays representing pixel data and other information that hardware expects to be formatted 'just so')

Plus, I don't know... it just seems silly to me to rebuild a class to act just like a basic type when you can just use a basic type. This just strikes me as overkill.
Last edited on Jun 5, 2009 at 1:53pm
Jun 5, 2009 at 2:10pm
In the WIN16 API, the objects operated on handles, and were all decleared as HANDLE. Functions from GlobalAlloc, to CreateWindow and beyond all returned the same type, HANDLE. But what that meant was, there was no static check to stop you from passing the handle from CreateWindow to GlobalFree.

Even in the WIN32 API, there's nothing to stop you from passing a handle from FindFirstFile to CloseHandle, which is wrong.

The WIN16 fix was to use a macro to define a different kind of handle (using struct) for each place it was different. It was turned on by defining STRICT. There was no space or runtime cost, but it caught all of these handle misuse errors at compile time.
Jun 5, 2009 at 2:25pm
That's not too related, though. HANDLE's definition is #define HANDLE void *
Last edited on Jun 5, 2009 at 2:44pm
Jun 5, 2009 at 2:38pm
@Disch:

1) Agreed. Templating is a bit tricky; you can have a templated base type but you still need a real object
that at a minimum has forwarding constructors.

2) Definitely agreed, but who cares about C anyway :)

3) It does, but as long as jsmith::GLubyte contains no virtual functions and contains only a single data member value, then sizeof( jsmith::GLubyte ) == sizeof( jsmith::GLubyte::value ) == sizeof( opengl::GLubyte ).
(Ok, I think that is what the standard says off the top of my head.)

And I have to agree with you that in performance critical code, speed wins out over safety where safety incurs runtime penalties.

On some days I agree with you overall. It is unfortunately a lot of work. On the other hand, I've had enough experiences where I wrote code that used some typedef'ed types and I ended up passing parameters in the wrong order because Foo and Bar were both typedefs for std::string and of course the compiler didn't see a problem. Function expected Foo and Bar, I passed Bar and Foo in some places and the right order in others. Eventually I gave up and created full types for them. Turned runtime errors into compile time errors.

(Of course I'm not trying to say that you should never use fundamental types; it's a matter of code complexity and the likelihood of encountering problems such as the one I described.)
Jun 5, 2009 at 2:43pm
It is related.

The point is sometimes it is useful to generate real types. I gave real-world example where it was useful. Additionally, when STRICT is defined in WIN16 a handles weren't void*. Further more, that lesson was forgotten in WIN32, and I gave an example where you can be caught out.

I also gave an example where typedef is the prefered thing to use and pointed out what I thought was wrong with the example given.

I'd wrap up by saying use features where they're helpful and solve a real problem, don't just use them to make an implementation look orthogonal.

What did you think wasn't related.
Last edited on Jun 5, 2009 at 2:43pm
Jun 5, 2009 at 3:13pm
but as long as jsmith::GLubyte contains no virtual functions and contains only a single data member value [snip]


Really? I guess that's what I get for assuming. I just recall that every time I tried to struct something it padded it to the next DWORD boundary. Perhaps I just never checked that for structs/classes which only contain a single member.
Last edited on Jun 5, 2009 at 3:13pm
Jun 5, 2009 at 3:19pm
kbw: *Shrug*
I got nothing.

I just recall that every time I tried to struct something it padded it to the next DWORD boundary.
Well, MinGW gives me 1 for sizeof() on a POD with just one char.
Jun 7, 2009 at 9:22am
"How might I benefit from using typedef?"

Misusing it may lead to the same benefit as a nickname instead of the name.
Also it remembers a length of the bytes row.
Jun 7, 2009 at 6:29pm
I use typedefs to minimize ugliness and make code more readable.

1
2
3
4
typedef std::vector<std::vector<int> >  Matrix;

Matrix a;
Matrix b;

Topic archived. No new replies allowed.