When Should I Use Typedef in C++

Why should we typedef a struct so often in C?

As Greg Hewgill said, the typedef means you no longer have to write struct all over the place. That not only saves keystrokes, it also can make the code cleaner since it provides a smidgen more abstraction.

Stuff like

typedef struct {
int x, y;
} Point;

Point point_new(int x, int y)
{
Point a;
a.x = x;
a.y = y;
return a;
}

becomes cleaner when you don't need to see the "struct" keyword all over the place, it looks more as if there really is a type called "Point" in your language. Which, after the typedef, is the case I guess.

Also note that while your example (and mine) omitted naming the struct itself, actually naming it is also useful for when you want to provide an opaque type. Then you'd have code like this in the header, for instance:

typedef struct Point Point;

Point * point_new(int x, int y);

and then provide the struct definition in the implementation file:

struct Point
{
int x, y;
};

Point * point_new(int x, int y)
{
Point *p;
if((p = malloc(sizeof *p)) != NULL)
{
p->x = x;
p->y = y;
}
return p;
}

In this latter case, you cannot return the Point by value, since its definition is hidden from users of the header file. This is a technique used widely in GTK+, for instance.

UPDATE Note that there are also highly-regarded C projects where this use of typedef to hide struct is considered a bad idea, the Linux kernel is probably the most well-known such project. See Chapter 5 of The Linux Kernel CodingStyle document for Linus' angry words. :) My point is that the "should" in the question is perhaps not set in stone, after all.

When should I use typedef in C++?

Template Metaprogramming

typedef is necessary for many template metaprogramming tasks -- whenever a class is treated as a "compile-time type function", a typedef is used as a "compile-time type value" to obtain the resulting type. E.g. consider a simple metafunction for converting a pointer type to its base type:

template<typename T>
struct strip_pointer_from;

template<typename T>
struct strip_pointer_from<T*> { // Partial specialisation for pointer types
typedef T type;
};

Example: the type expression strip_pointer_from<double*>::type evaluates to double. Note that template metaprogramming is not commonly used outside of library development.

Simplifying Function Pointer Types

typedef is helpful for giving a short, sharp alias to complicated function pointer types:

typedef int (*my_callback_function_type)(int, double, std::string);

void RegisterCallback(my_callback_function_type fn) {
...
}

When to use typedef in c?

So..

You can do this:

struct node {
int data;
struct node* forwardLink;
};

To define an object that you can use as struct node.

Like this:

struct node x;

However, say you wanted to refer to it as just node. Then you could do:

struct node {
int data;
struct node* forwardLink;
};

typedef struct node node;

or

 typedef struct {
int data;
void* forwardLink;
} node;

and then use that as:

node x;

typedef struct vs struct definitions

The common idiom is using both:

typedef struct S { 
int x;
} S;

They are different definitions. To make the discussion clearer I will split the sentence:

struct S { 
int x;
};

typedef struct S S;

In the first line you are defining the identifier S within the struct name space (not in the C++ sense). You can use it and define variables or function arguments of the newly defined type by defining the type of the argument as struct S:

void f( struct S argument ); // struct is required here

The second line adds a type alias S in the global name space and thus allows you to just write:

void f( S argument ); // struct keyword no longer needed

Note that since both identifier name spaces are different, defining S both in the structs and global spaces is not an error, as it is not redefining the same identifier, but rather creating a different identifier in a different place.

To make the difference clearer:

typedef struct S { 
int x;
} T;

void S() { } // correct

//void T() {} // error: symbol T already defined as an alias to 'struct S'

You can define a function with the same name of the struct as the identifiers are kept in different spaces, but you cannot define a function with the same name as a typedef as those identifiers collide.

In C++, it is slightly different as the rules to locate a symbol have changed subtly. C++ still keeps the two different identifier spaces, but unlike in C, when you only define the symbol within the class identifier space, you are not required to provide the struct/class keyword:

 // C++
struct S {
int x;
}; // S defined as a class

void f( S a ); // correct: struct is optional

What changes are the search rules, not where the identifiers are defined. The compiler will search the global identifier table and after S has not been found it will search for S within the class identifiers.

The code presented before behaves in the same way:

typedef struct S { 
int x;
} T;

void S() {} // correct [*]

//void T() {} // error: symbol T already defined as an alias to 'struct S'

After the definition of the S function in the second line, the struct S cannot be resolved automatically by the compiler, and to create an object or define an argument of that type you must fall back to including the struct keyword:

// previous code here...
int main() {
S();
struct S s;
}

Why not using a typedef for a struct in C?

I consider typedef harmful. I think it almost invariably mis-used. In my opinion, it is useful and only useful when the underlying type is FULLY abstracted - e.g. ALL manipulation is through functions. Otherwise, the underlying type is not abstracted - the user still has to know what it is, to make sense of the code - all that has happened is that code readabiliy has been impaired, where the type namespace has been without gain (for the type is not properly abstracted) extended.

In my view, almost all use of typedef lacks understanding of the problems it brings and how it ought to be used.

what is the use of using the typedef and what does it signifies?

Note, this is for C and not C++. Maybe it's applicable for C++ too, but I don't know.

typedef a b makes b an alias for a

What typedef struct alpha *Abc; is to make Abc an alias for struct alpha *. After you have created this, these two are equivalent:

struct alpha *ptr;
Abc ptr;

There are a few different uses for this:

It reduces how much you need to write. You can skip the struct word for instance. Many would say that this is NOT a good reason to use typedef.

It can give a more descriptive name for certain things. Examples:

typedef char* string; // I would never use this. It's just an example.
typedef int[3] vector; // However, this is something I would consider.

It can make it easier to change the type for a large code base in certain situations. Suppose you have this code:

int16_t foo(int16_t a)
{
int16_t b = a+1;
for(int16_t = 0; i<b; i++) {
...

And later you realize that it would be a good idea to change to int32_t instead. If the code instead looked like this, you would only need to change one line:

typedef int16_t T
T foo(T a)
{
T b = a+1;
for(T = 0; i<b; i++) {
...

Be restrictive with typedefs. Often they just clutters the code and makes it harder to follow. Use them when they fill a purpose. Be especially restrictive with aliasing pointers. I only use them for function pointers and completely opaque objects. Same thing with structs and unions. IMHO, 99% of the typedefs I see in questions here at SO are not necessary and seems to be there just because people think that they should.

If you consider typedefing a struct, ask yourself if it would be good to anyone reading the code to know that a declaration is a struct. If yes, declare it with the struct keyword. Of course you could invent something like typedef struct my_struct my_struct_s but is it really worth the effort? I'd say no.

It can be worth noting that C has something that seems like a simple form of namespace. This means that this completely valid:

typedef struct mystruct mystruct;
struct mystruct a;
mystruct b;

So mystruct is an alias for mystruct. And this is what you should do, unless you have a good reason not to.

I once wrote a related answer and if that answer wasn't so heavily focused on just structs, I would have made this question a duplicate.

In comments below, I saw this example:

typedef unsigned short int int_16;

and this is a HORRIBLE example. First, int_16 gives the impression that it is a signed type, which it is clearly not. Secondly, short int (and its unsigned counterpart) is guaranteed to have at least 16 bits, but int_16 gives the impression that it have exactly 16 bits. Depending on what you want, there are a few good ones in the standard:

  • int16_t and uint16_t - Exactly 16 bits
  • int_least16_t and uint_least16_t - At least 16 bits
  • int_fast16_t and uint_fast16_t - At least 16 bits, but hint to the compiler that you want the fastest type

How to properly use `typedef` for structs in C?

What this does:

typedef struct {
int a;
int b;
} ab_t;

Is define an anonymous struct and give it the alias ab_t. For this case there's no problem as you can always use the alias. It would however be a problem if one of the members was a pointer to this type.

If for example you tried to do something like this:

typedef struct {
int count;
TNODE *left, *right;
} TNODE;

This wouldn't work because the type TNODE is not yet defined at the point it is used, and you can't use the tag of the struct (i.e. the name that comes after the struct keyword) because it doesn't have one.

What is the difference between 'typedef' and 'using' in C++11?

All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.



Typedef declarations can, whereas alias declarations cannot(+), be used as initialization statements

But, with the first two non-template examples, are
there any other subtle differences in the standard?

  • Differences in semantics: none.
  • Differences in allowed contexts: some(++).

(+) P2360R0 (Extend init-statement to allow alias-declaration) has been approved by CWG and as of C++23, this inconsistency between typedef declarations and alias declarations will have been removed.
(++) In addition to the examples of alias templates, which has already been mentioned in the original post.

Same semantics

As governed by [dcl.typedef]/2 [extract, emphasis mine]

[dcl.typedef]/2 A
typedef-name
can also be introduced by an
alias-declaration.
The identifier following the using keyword becomes a
typedef-name and the optional attribute-specifier-seq following the identifier appertains to that typedef-name. Such a
typedef-name has the same semantics as if it were introduced by the typedef specifier.
[...]

a typedef-name introduced by an alias-declaration has the same semantics as if it were introduced by the typedef declaration.

Subtle difference in allowed contexts

However, this does not imply that the two variations have the same restrictions with regard to the contexts in which they may be used. And indeed, albeit a corner case, a typedef declaration is an init-statement and may thus be used in contexts which allow initialization statements

// C++11 (C++03) (init. statement in for loop iteration statements).
for (typedef int Foo; Foo{} != 0;)
// ^^^^^^^^^^^^^^^ init-statement
{
}

// C++17 (if and switch initialization statements).
if (typedef int Foo; true)
// ^^^^^^^^^^^^^^^ init-statement
{
(void)Foo{};
}

switch (typedef int Foo; 0)
// ^^^^^^^^^^^^^^^ init-statement
{
case 0: (void)Foo{};
}

// C++20 (range-based for loop initialization statements).
std::vector<int> v{1, 2, 3};
for (typedef int Foo; Foo f : v)
// ^^^^^^^^^^^^^^^ init-statement
{
(void)f;
}

for (typedef struct { int x; int y;} P; auto [x, y] : {P{1, 1}, {1, 2}, {3, 5}})
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ init-statement
{
(void)x;
(void)y;
}

whereas an alias-declaration is not an init-statement, and thus may not be used in contexts which allows initialization statements

// C++ 11.
for (using Foo = int; Foo{} != 0;) {}
// ^^^^^^^^^^^^^^^ error: expected expression

// C++17 (initialization expressions in switch and if statements).
if (using Foo = int; true) { (void)Foo{}; }
// ^^^^^^^^^^^^^^^ error: expected expression

switch (using Foo = int; 0) { case 0: (void)Foo{}; }
// ^^^^^^^^^^^^^^^ error: expected expression

// C++20 (range-based for loop initialization statements).
std::vector<int> v{1, 2, 3};
for (using Foo = int; Foo f : v) { (void)f; }
// ^^^^^^^^^^^^^^^ error: expected expression

Usage of Typedef Struct in C programming

The template you were given is this, where I've added numbers to some of the lines for ease of reference:

typedef struct NODE_s *NODE;    // 1
typedef struct NODE_s // 2
{
NODE right; // 3
NODE left;
unsigned long data;
int height;
} NODE_t[1]; // 4

typedef struct TREE_s *TREE;
typedef struct TREE_s
{
NODE root;
} TREE_t[1];

TREE tree_init(); // 5
NODE node_init(unsigned long data);

What are the problems here?

  1. As noted in comments, the SO Q&A Is it a good idea to typedef pointers suggests that it is not a good idea to typedef pointers, with limited exceptions for 'pointers to functions' (not relevant here) and perhaps (but probably not) for opaque types. This line does two things: (1) it says "there is a structure type with the tag NODE_s; (2) the name NODE is a synonym for struct NODE_s *. The structure type is incomplete at the moment; no details are known about its members.
  2. This line starts a new typedef, but also starts the definition of the type struct NODE_s (because of the { that follows on the next line).
  3. The type NODE is already known; it can be used here. It means the same as if you wrote struct NODE_s *right;.
  4. The name NODE_t is an alias for the type struct NODE_s[1], an array of 1 struct NODE_s. It isn't clear what this is going to be used for. I have reservations (at best) about its existence. (You can also apply the discussion in points 1, 2, 4 to the struct TREE_s type, mutatis mutandis.)
  5. This is a function declaration, but it is not a prototype declaration. It says that the tree_init() function can be called with any number of arguments of any type because no information is specified about the number or type of those arguments. We do know it is not a variadic function (variable argument list, like printf()) because those must have a full prototype declaration ending with , ...) in scope before they're used. If you want to specify that the function takes no arguments, say so: TREE tree_init(void);.

I think the intent behind the NODE_t and TREE_t types is to allow you to write, for example:

int main(void)
{
TREE_t x = { 0 };

if (x->root != 0)
return 1;
return 0;
}

I'm not convinced whether that's sufficiently helpful to warrant the type compared with using struct NODE_s x and using x.root in the test. It does save you from having to add an & when passing a pointer to a function; again, I'm not sure it is really sufficiently helpful to warrant its existence.

What I would prefer to see as the template is:

typedef struct NODE_s NODE;
struct NODE_s
{
NODE *right;
NODE *left;
unsigned long data;
int height;
};

typedef struct TREE_s TREE;
struct TREE_s
{
NODE *root;
};

extern TREE *tree_init(void);
extern NODE *node_init(unsigned long data);

This removes the pointers from the typedef statements, avoids the somewhat peculiar array types, and uses an explicit prototype for tree_init(). I personally prefer to have function declarations marked with extern; in a header, they'll match the extern on those rare global variables that are declared in the header. Many people prefer not to use extern because the compiler assumes that anyway — so be it; the most important thing is consistency.

The code in main() would now be written:

int main(void)
{
TREE x = { 0 };

if (x.root != 0)
return 1;
return 0;
}

The difference? An arrow -> changed to a dot .. Not a lot of problem there. However, when calling functions, you'd probably use &x whereas with the TREE_t type, you would just write x (because it's an array).



Related Topics



Leave a reply



Submit