2

I think usually typedef can be use like this:

typedef int INT

The first word is some keyword in C/C++ and the second one is the alias of the first one.

But recently I noticed this statement

typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);
GL_GENBUFFERS glGenBuffers  = (GL_GENBUFFERS)wglGetProcAddress("glGenBuffers");
GLuint buffer;
glGenBuffers(1, &buffer);

It seems that behind typedef there are three words, so how could compiler parse this statement?

Thanks!

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
zjnyly
  • 189
  • 1
  • 11
  • 2
    [This may help you out](https://stackoverflow.com/a/44990464/4581301) and offer a (my opinion) more readable solution in C++. – user4581301 Sep 03 '21 at 15:15
  • 1
    The way I always kept it straight in my head was to think of it like this: In a `typedef,` the compiler looks at the entire expression and picks out *the only thing it has never heard of before*. and assumes that is what you are typedef-ing. So `INT` in the first expression and `GL_GENBUFFERS` in the second. I doubt that's technically correct but it helps me keep things straight. – Joe Sep 03 '21 at 15:22
  • It still needs to appear in that location, after the `void` for the compiler to realize it's a function pointer and not something else – Joe Sep 03 '21 at 15:28
  • In C++11 and later, prefer `using` instead, which is a more powerful and cleaner `typedef`, eg: `using INT = int; using GL_GENBUFFERS = void (*)(GLsizei, GLuint*);` – Remy Lebeau Sep 03 '21 at 19:33

3 Answers3

5

Let's start by talking about declaration syntax in general (I'll be using C terminology, although C++ is largely similar). In both C and C++, a declaration contains a sequence of one or more declaration specifiers followed by a comma-separated list of zero or more declarators.

Declaration specifiers include type specifiers (int, double, char, unsigned, etc.), type qualifiers (const, volatile, etc.), storage class specifiers (static, register, typedef, etc.), struct and union specifiers, and a few other things we won't get into here.

Declarators include the name of the thing being declared, along with information about that thing's pointer-ness, array-ness, or function-ness (in C++ you also have reference-ness).

When you declare a function, such as

void foo( int, double );

void is the declaration specifier (type specifier), and foo( int, double ) is the declarator. The type of foo is fully specified by the combination of the declaration specifier and declarator:

     foo                     -- foo
     foo(             )      -- is a function taking
     foo(             )      --   unnamed parameter
     foo( int         )      --   is an int
     foo( int,        )      --   unnamed parameter
     foo( int, double )      --   is a double
void foo( int, double )      -- returning void

In plain English, the type of foo is "function taking an int and double parameter and returning void."

You can declare pointers to functions as well:

       fptr                  -- fptr
     (*fptr)                 -- is a pointer to
     (*fptr)(             )  --   function taking
     (*fptr)(             )  --     unnamed parameter
     (*fptr)( int         )  --     is an int
     (*fptr)( int,        )  --     unnamed parameter
     (*fptr)( int, double )  --     is a double
void (*fptr)( int, double )  --   returning void

Again, the sole declaration specifier is void, and the declarator is (*fptr)( int, double ).

For syntactic purposes, typedef is grouped with the storage class specifiers (static, auto, register), but it doesn't behave like other storage class specifiers - instead of affecting the storage or visibility of the thing being declared, it makes the identifier in the declarator an alias for the type. If we stick typedef on the front of the above declaration:

typedef void (*fptr)( int, double );

then it reads as

               fptr                   -- fptr
typedef        fptr                   -- IS AN ALIAS FOR THE TYPE
typedef      (*fptr)                  --   pointer to
typedef      (*fptr)(             )   --     function taking
typedef      (*fptr)(             )   --       unnamed parameter
typedef      (*fptr)( int         )   --       is an int
typedef      (*fptr)( int,        )   --       unnamed parameter
typedef      (*fptr)( int, double )   --       is a double
typedef void (*fptr)( int, double )   --     returning void

IOW, fptr is an alias (typedef name) for the type "pointer to function taking an int and double parameter and returning void", and you can use it to declare pointer objects of that type:

fptr fp1, fp2;

You can do the same thing with other pointer types1:

typedef int *intp;         // intp is an alias for the type "pointer to int";
typedef double (*arr)[10]; // arr is an alias for the type "pointer to 10-element array of double"

Declarators can get pretty complex. You can have pointers to functions:

T (*ptr)();

pointers to arrays:

T (*ptr)[N];

arrays of pointers to functions:

T (*ptr[N])();

functions returning pointers to arrays:

T (*foo())[N];

arrays of pointers to functions returning pointers to arrays:

T (*(*arr[N])())[M];

and on and on and on, and sticking typedef in front of any of them will work:

typedef T (*(*arr[N])())[M];

means arr is an alias for the type "N-element array of pointers to functions returning pointers to M-element arrays of T".


  1. As a rule, you do not want to hide pointers to scalar types behind typedefs unless you can guarantee that the programmer using that type will never have to be aware of the underlying pointer-ness of that type (i.e., will never have to explicitly dereference it with * or try to print its value with %p or anything like that).
John Bode
  • 119,563
  • 19
  • 122
  • 198
2

If you have a function of the type

void (GLsizei, GLuint*);

then a pointer to the function type will look like

void (*GL_GENBUFFERS) (GLsizei, GLuint*);

If you add the typedef specifier like

typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);

then in this case GL_GENBUFFERS is not an object of a pointer type but an alias for the pointer type void (*) (GLsizei, GLuint*).

Another approach is at first to introduce an alias for a function type as for example

typedef void GL_GENBUFFERS(GLsizei, GLuint*);

Then the pointer type to the function of the type void (GLsizei, GLuint*) will look like GL_GENBUFFERS *

In C++ you can use an alias declaration like

using GL_GENBUFFERS = void ( * )(GLsizei, GLuint*);
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
2

Let's consider the following code

typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);

int main()
{
   static_assert(std::is_same<GL_GENBUFFERS, void(*)(GLsizei, GLuint*)>::value,"Not same")
}

If you try, you can see that the code above, you will not see the message Not same, which means that those two have the same type.

You can also do something like this

typedef void(SomeClass::*member_func_ptr)(int, int);

The type of member_func_ptr is, as the name suggests, a pointer to member function of class SomeClass, accepting parameters of type int, and returning void.

Karen Baghdasaryan
  • 2,407
  • 6
  • 24