Repeated typedefs - invalid in C but valid in C++?

Solution 1:

Why does this compile in C++?

Because the C++ Standard explicitly says so.

Reference:

C++03 Standard 7.1.3 typedef specifier

§7.1.3.2:

In a given non-class scope, a typedef specifier can be used to redefine the name of any type declared in that scope to refer to the type to which it already refers.

[Example:
typedef struct s { /* ... */ } s;
typedef int I;
typedef int I;
typedef I I;
—end example]

Why does this fail to compile in C?

typedef names have no linkage and C99 standard disallows identifiers with no linkage specification to have more than one declaration with the same scope and in the same name space.

Reference:

C99 Standard: §6.2.2 Linkages of identifiers

§6.2.2/6 states:

The following identifiers have no linkage: an identifier declared to be anything other than an object or a function; an identifier declared to be a function parameter; a block scope identifier for an object declared without the storage-class specifierextern.

Further §6.7/3 states:

If an identifier has no linkage, there shall be no more than one declaration of the identifier (in a declarator or type specifier) with the same scope and in the same name space, except for tags as specified in 6.7.2.3.

Solution 2:

Standard C is now ISO/IEC 9989:2011

The 2011 C standard was published on Monday 2011-12-19 by ISO (or, more precisely, the notice that it had been published was added to the committee web site on the 19th; the standard may have been published as 'long ago' as 2011-12-08). See the announcement at the WG14 web site. Sadly, the PDF from ISO costs 338 CHF, and from ANSI 387 USD.

  • You can obtain the PDF for INCITS/ISO/IEC 9899:2012 (C2011) from ANSI for 30 USD.
  • You can obtain the PDF for INCITS/ISO/IEC 14882:2012 (C++2011) from ANSI for 30 USD.

Main Answer

The question is "Are repeated typedefs allowed in C"? The answer is "No - not in the ISO/IEC 9899:1999 or 9899:1990 standards". The reason is probably historical; the original C compilers did not allow it, so the original standardizers (who were mandated to standardize what was already available in C compilers) standardized that behaviour.

See the answer by Als for where the C99 standard proscribes repeated typedefs. The C11 standard has changed the rule in §6.7 ¶3 to:

3 If an identifier has no linkage, there shall be no more than one declaration of the identifier (in a declarator or type specifier) with the same scope and in the same name space, except that:

  • a typedef name may be redefined to denote the same type as it currently does, provided that type is not a variably modified type;
  • tags may be redeclared as specified in 6.7.2.3.

So there is now an explicit mandate a repeated typedef in C11. Roll on the availability of C11-compliant C compilers.


For those still using C99 or earlier, the follow-up question is then, presumably "So how do I avoid running into problems with repeated typedefs?"

If you follow the rule that there is a single header that defines each type that is needed in more than one source file (but there can be many headers defining such types; each separate type is found in only one header, though), and if that header is used any time that type is needed, then you don't run into the conflict.

You can also use incomplete structure declarations if you only need pointers to the types and don't need to allocate the actual structure or access the members of them (opaque types). Again, set rules about which header declares the incomplete type, and use that header wherever the type is needed.

See also What are extern variables in C; it talks about variables, but types can be treated somewhat similarly.


Question from Comments

I very much need the "incomplete structure declarations", on account of separate preprocessor complications that prohibit certain inclusions. So you're saying that I must not typedef those forward declarations if they're typedefed again by the full header?

More or less. I've not really had to deal with this (though there are parts of the systems at work that get very close to having to worry about it), so this is a little tentative, but I believe it should work.

Generally, a header describes the external services provided by a 'library' (one or more source files) in sufficient detail for the users of the library to be able to compile with it. Especially in the case where there are multiple source files, there may also be an internal header that defines, for example, the complete types.

All headers are (a) self-contained and (b) idempotent. That means you can (a) include the header and all the required other headers are automatically included, and (b) you can include the header multiple times without incurring the wrath of the compiler. The latter is usually achieved with header guards, though some prefer #pragma once - but that is not portable.

So, you can have a public header like this:

public.h

#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDED

#include <stddef.h>    // size_t

typedef struct mine mine;
typedef struct that that;

extern size_t polymath(const mine *x, const that *y, int z);

#endif /* PUBLIC_H_INCLUDED */

So far, so not very controversial (though one can legitimately suspect that the interface provided by this library is very incomplete).

private.h

#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED

#include "public.h"  // Get forward definitions for mine and that types

struct mine { ... };
struct that { ... };

extern mine *m_constructor(int i);
...

#endif /* PRIVATE_H_INCLUDED */

Again, not very controversial. The public.h header must be listed first; this provides an automatic check of self-containment.

Consumer code

Any code that needs the polymath() services writes:

#include "public.h"

That is all the information that's needed to use the service.

Provider code

Any code in the library that defines the polymath() services writes:

#include "private.h"

Thereafter, everything functions as normal.

Other provider code

If there's another library (call it multimath()) that uses the polymath() services, then that code gets to include public.h just like any other consumer. If the polymath() services are part of the external interface to multimath(), then the multimath.h public header will include public.h (sorry, I switched terminologies near the end, here). If the multimath() services completely conceal the polymath() services, then the multimath.h header won't include public.h, but the multimath() private header might well do so, or the individual source files that need the polymath() services can include it when needed.

As long as you religiously follow the discipline of including the correct header everywhere, then you won't run into double-definition trouble.

If you subsequently find that one of your headers contains two groups of definitions, one which can be used without conflict and one which can sometimes (or always) conflict with some new header (and the services declared therein), then you need to split the original header into two sub-headers. Each sub-header individually follows the rules elaborated here. The original header becomes trivial - a header guard and lines to include the two individual files. All existing working code remains untouched - though the dependencies change (extra files to depend on). New code can now include the relevant acceptable sub-header while also using the new header that conflicts with the original header.

Of course, you can have two headers that are simply irreconcilable. For a contrived example, if there is a (badly designed) header that declares a different version of the FILE structure (from the version in <stdio.h>), you are hosed; code can include either the badly designed header or <stdio.h> but not both. In this case, the badly designed header should be revised to use a new name (perhaps File, but perhaps something else). You might more realistically run into this trouble if you have to merge code from two products into one after a corporate takeover, with some common data structures, such as DB_Connection for a database connection. In the absence of the C++ namespace feature, you are stuck with a renaming exercise for one or both lots of code.

Solution 3:

You can do it in C++ because of 7.1.3/3 and /4.

You can't do it in C99 because it doesn't have any equivalent special case in 6.7.7, so re-declaring a typedef name follows the same rules as re-declaring any other identifier. Specifically 6.2.2/6 (typedefs have no linkage) and 6.7/3 (identifiers with no linkage can only be declared once with the same scope).

Remember typedef is a storage-class-specifier in C99, whereas in C++ it's a decl-specifier. The different grammar leads me to suspect that the C++ authors decided to put more effort into making typedefs "a different kind of declaration", and so may well have been willing to spend more time and text on special rules for them. Beyond that I don't know what the C99 authors' (lack of) motivation was.

[Edit: see Johannes's answer for C1x. I'm not following that at all, so I should probably stop using "C" to mean "C99" because I probably won't even notice when they ratify and publish. It's bad enough as it is: "C" should mean "C99", but in practice means "C99 if you're lucky, but if you have to support MSVC then C89".]

[Edit again: and indeed, it has been published and is now C11. Woot.]