Forward Declaration of a Typedef in C++

How to forward typedef'd struct in .h

Move the typedef struct Preprocessor Prepro; to the header the file and the definition in the c file along with the Prepro_init definition. This is will forward declare it for you with no issues.

Preprocessor.h

#ifndef _PREPROCESSOR_H_
#define _PREPROCESSOR_H_

#define MAX_FILES 15

typedef struct Preprocessor Prepro;

void Prepro_init(Prepro* p);

#endif

Preprocessor.c

#include "Preprocessor.h"

#include <stdio.h>

struct Preprocessor {
FILE fileVector[MAX_FILES];
int currentFile;
};

void Prepro_init(Prepro* p) {
(*p).currentFile = 0;
}

C Structure typedef with Forward Declarations

Remember that a typedef is just an alternative name for a type. There's a reason the Linux kernel doesn't use typedefs for structure types, and you're demonstrating it.

There are a couple of ways around your problem. I note that C11 does allow multiple occurrences of the same typedef, but I'm assuming you're stuck with an older compiler that does not support that.

TL;DR

Even though the Linux kernel doesn't use typedefs, I usually do use typedefs, but I mostly avoid mutually referential structure types (I can't think of any code where I used such a type). And, like Jens Gustedt notes in his answer, I almost invariably use the notations:

typedef struct SomeTag SomeTag;

so that the type name and the structure tag are the same (they're in different namespaces). This operation is not necessary in C++; when you define struct SomeTag or class SomeTag, the name SomeTag becomes a type name without the need for an explicit typedef (though a typedef does no harm other than revealing that the author is more experienced in C than C++, or the code originated as C code).

I also observe that names starting with an underscore are best treated as 'reserved for the implementation'. The rules are a little more complex than that, but you run a risk of the implementation usurping your names — and being within its rights to usurp your names — when you use names starting with an underscore, so don't. Likewise, POSIX reserves type names ending _t for the implementation if you include any POSIX headers (such as <stdio.h> when you aren't compiling in strict Standard C only mode). Avoid creating such names; they'll hurt you sooner or later. (I've not fixed the code below to deal with either of these issues: caveat emptor!).

In the code fragments below, I'm mostly ignoring the code that prevents multiple inclusions (but you should have it in your code).

Extra header

typedefs.h:

typedef struct Object Object_t;
typedef struct Klass Klass_t;

klass.h

#include "typedefs.h"

struct Klass
{
void (*_initialize)(Object_t *object, Klass_t *klass);
};

object.h

#include "typedefs.h"

struct Object
{
Klass_t *_klass;
};

This works because the two type names Klass_t and Object_t are declared before they're used.

Use struct Object in prototype

klass.h

typedef struct Klass Klass_t;
struct Object;

struct Klass
{
void (*_initialize)(struct Object *object, Klass_t *klass);
};

Or, for consistency, it might even use:

    void (*_initialize)(struct Object *object, struct Klass *klass);

object.h

#include "klass.h"

struct Object
{
Klass_t *_klass;
};

This works because (within broad limits — basically, if the types are defined at file scope, not inside a function) struct Object always refers to the same type, regardless of whether all the details are fully defined yet.

GCC 4.8.2

Under all of -std=c89, -std=c99 and -std=c11, GCC 4.8.2 accepts replicated typedefs, as in the code below. It requires -std=c89 -pedantic or -std=c99 -pedantic to get errors about the repeated typedefs.

Even without the -pedantic option, GCC 4.5.2 rejects this code; however, GCC 4.6.0 and later versions accept it without the -pedantic option.

klass.h

#ifndef KLASS_H_INCLUDED
#define KLASS_H_INCLUDED

typedef struct Klass Klass_t;
typedef struct Object Object_t;

struct Klass
{
void (*_initialize)(Object_t *object, Klass_t *klass);
};

#endif /* KLASS_H_INCLUDED */

object.h

#ifndef OBJECT_H_INCLUDED
#define OBJECT_H_INCLUDED

typedef struct Klass Klass_t;
typedef struct Object Object_t;

struct Object
{
Klass_t *klass;
};

#endif /* OBJECT_H_INCLUDED */

consumer.c

#include "klass.h"
#include "object.h"

Klass_t k;
Object_t o;

You'll have to decide whether that's a risk you're willing to take for your code — how important is portability, and to which versions of C (and which C compilers) must it be portable.

how to create a forward declaration of a typedef struct

Unfortunately typedefs cannot be forward-declared.

A common workaround is to have a C++ class that inherits from the C struct, referenced by its typedef, and you can forward-declare that. This will require some code changes, but they should be minimal.

(Posting on behalf of https://stackoverflow.com/users/3943312/sam-varshavchik who commented)

C forward declaration for typedef struct

Here are the relevant sections from the C standard (emphasis added):

§6.2.5p1

At various points within a translation unit an object type may be
incomplete (lacking sufficient information to determine the size of
objects of that type) or complete (having sufficient information).

§6.7.2p3

A structure or union shall not contain a member with incomplete or
function type (hence, a structure shall not contain an instance of
itself, but may contain a pointer to an instance of itself), except
that the last member of a structure with more than one named member
may have incomplete array type; such a structure (and any union
containing, possibly recursively, a member that is such a structure)
shall not be a member of a structure or an element of an array.

The first typedef declares an incomplete type called wheels. The car structure uses that incomplete type as a member. That's explicitly forbidden by the standard.

That's what the first error message is telling you. The other two error messages are just noise. They are the result of the fact that the compiler did not have sufficient information to complete the car structure.

As mentioned in the other answer, one of the uses of incomplete types is to declare pointers. For example, a node in a linked list where the structure contains a pointer to itself:

typedef struct node Node;   // struct node and Node are incomplete types here

struct node
{
int value;
Node *next; // using an incomplete type to declare a pointer
}; // struct node and Node are complete from here forward

How to correctly forward declare a typedef'd struct

Sorry. I just realized this was not a problem of my forward declaration but rather because what I really did in my actual code was this:

void get_property() {
some = assignment_statement;
myStruct *structure = NULL;
}

I.e. I accidentally put my variable declaration + definition below the first code statement. So indeed, I had oversimplified my code fragment. Sorry for this.

Forward Declaration of a typedef to a Primitive Type

So it turns out that you can do the same typedef more than once, in c++ only, not in c:

Once declared, a typedef-name may only be redeclared to refer to the same type again [1]

As such, just as you would use class foo to forward declare foo if it was a type you may also do:

typedef int foo

Then your typedef vector<foo> foos will compile fine. This won't violate the principle of Single Source of Truth because the compiler will enforce that all typedefs match. If you decide to change the type though it will require you to also go edit all the files that it was also defined in to match.

Forward declaration of a function typedef in C

You can do this by taking advantage of the fact that C specifies that a function declaration with no arguments means it takes an indeterminate number of arguments.

So you could do it as follows:

typedef void (*function_A_t)(void (*)());
typedef void (*function_B_t)(function_A_t f_A);

Which allows the following to compile:

void A(function_B_t b)
{
b(A);
}

void B(function_A_t a)
{
a(B);
}

int main()
{
function_A_t a = A;
function_B_t b = B;
a(B);
b(A);
return 0;
}

Section 6.7.6.3p15 of the C standard states the following regarding the compatibility of function types:

For two function types to be compatible, both shall specify
compatible return types. Moreover, the parameter type lists, if
both are present, shall agree in the number of parameters and
in use of the ellipsis terminator; corresponding parameters
shall have compatible types. If one type has a parameter type list
and the other type is specified by a function declarator that is
not part of a function definition and that contains an empty
identifier list, the parameter list shall not have an ellipsis
terminator and the type of each parameter shall be compatible with
the type that results from the application of the default
argument promotions.
If one type has a parameter type list and the
other type is specified by a function definition that contains a
(possibly empty) identifier list, both shall agree in the number
of parameters, and the type of each prototype parameter shall
be compatible with the type that results from the application
of the default argument promotions to the type of the
corresponding identifier. (In the determination of type
compatibility and of a composite type, each parameter declared
with function or array type is taken as having the adjusted type
and each parameter declared with qualified type is taken as having the
unqualified version of its declared type.)

The part in bold above specifies that void (*)() is compatible with void (*)(function_B_t)



Related Topics



Leave a reply



Submit