21

This gives an error in C++ but not in C:

typedef struct nodes
{
    int data;
    struct node *next;
}node;

It gives the following error in C++.

/home/DS cpp/linkedlist.cpp|10|error: conflicting declaration ‘typedef struct nodes node’|
/home/DS cpp/linkedlist.cpp|9|error: ‘struct node’ has a previous declaration as ‘struct node’|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

For it to work in C++ I have to change it to this:

typedef struct node
{
    int data;
    struct node *next;
}node;

I don't understand why this happens, I want to know the order of execution in both C and C++ so that I can understand it.

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
Achyut Rastogi
  • 1,335
  • 2
  • 11
  • 15
  • 13
    You know that you don't need to use the `typedef` in C++, right? The right way to "get it to work" would be `struct node { int data; node* next;};`. – juanchopanza Aug 09 '15 at 11:16
  • 7
    What does "order of execution" have to do with anything? – Lightness Races in Orbit Aug 09 '15 at 11:21
  • 3
    @alk, it is valid in C, `struct node*` declares a new type, distinct from `struct nodes` – Jonathan Wakely Aug 09 '15 at 12:11
  • @JonathanWakely, huhu, code blocks ... :} – alk Aug 09 '15 at 12:33
  • In the C version, if you actually use the next field as a (node*) pointer, you'll get incompatible pointer type warnings. I think it's lousy that there's no warning otherwise. It should be an outright error declare a pointer to a type that doesn't become known by the end of the compilation. – Mike Housky Aug 09 '15 at 13:04
  • @MikeHousky: The pointer is known, just not what it is referring to, besides that it's a `struct` object. The pointer is writeable and readable. – alk Aug 09 '15 at 13:15
  • 1
    @MikeHousky: It's kinda necessary to allow declarations of pointers to incomplete types. – Lightness Races in Orbit Aug 09 '15 at 16:31
  • @MikeHousky, it's a common and useful idiom to define APIs in terms of pointers to opaque types. Requiring all declared types to be complete in all translation units would make that impossible, so is a bad idea and will never happen. – Jonathan Wakely Aug 11 '15 at 14:17

2 Answers2

27

Let's analyse your code a bit:

typedef struct nodes
{
    int data;
    struct node *next;
}node;

This declares and defines struct nodes, a type with two members, and declares a type alias so we can refer to it only as node.

Now, in C++, the member declaration struct node *next automatically forward-declares a type called node. That then conflicts with your typedef target node: it's as if you're trying to give two types the same name.

In C, there is no conflict, because the type called node can in fact only be referred to as struct node.

The second snippet worked because, since during parsing of the member declaration struct node already exists, no new type is forward-declared there … and since all you're then doing is renaming it in the same typedef statement, C++ doesn't really care, knowing that it's all the same type (struct T is T; the difference is in syntax, not in name).

[C++11: 7.1.3/3]: 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 ]

[C++11: 7.1.3/6]: In a given scope, a typedef specifier shall not be used to redefine the name of any type declared in that scope to refer to a different type. [ Example:

class complex { / ... / };
typedef int complex; // error: redefinition

—end example ]

Of course, in C++, this is all moot and you should just write:

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

You do not need to typedef-away the elaborated-type-specifier struct.

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 13
    When you have to get that deep into a 1,338 page document (library starts on page 424, so the language part is only about a third of the whole pile) just to answer basic questions about names and scopes, you can see why some people think C++ is too complex. :^) – Mike Housky Aug 09 '15 at 11:55
  • 2
    @MikeHousky: It's ridiculous – Lightness Races in Orbit Aug 09 '15 at 11:56
  • Thanks for bringing up the restrictions on C++ tag names, though. – Mike Housky Aug 09 '15 at 11:56
  • Unless, of course, you are writing a header-file for both C *and* C++. In that case declaring a typedef-name the same as the struct-tag is really common, and while not *neccessarily* better than leaving out the typedef, at a minimum better than using a different name for both. – Deduplicator Aug 09 '15 at 15:55
  • @Deduplicator: In that case what you're really doing is writing a header file for C, since C++ is largely backwards-compatible but the reverse is certainly not true. – Lightness Races in Orbit Aug 09 '15 at 15:57
  • Mostly. But the <1% are always the sticky point... like rules for `inline`, booleans, and so... And maybe the interface should be a bit *richer* if included in C++. – Deduplicator Aug 09 '15 at 15:58
  • Not seeing what fits into the <1%. – Lightness Races in Orbit Aug 09 '15 at 15:59
  • @MikeHousky: The C++ standard document is not a textbook or a tutorial. That's just not what it's for. You don't use it to learn the language. – Christian Hackl Aug 09 '15 at 16:16
  • @ChristianHackl: He did not claim so. – Lightness Races in Orbit Aug 09 '15 at 16:17
  • 2
    @LightnessRacesinOrbit: It certainly sounded like it. I can also not help but mention that the Java 8 language specification is a 788-pages PDF, and that's just the language, no platform API or VM specification. Yet strangely nobody seems to think that this makes Java too complex. I don't think that serious programming languages can be specified in a complete and simple manner at the same time. – Christian Hackl Aug 09 '15 at 16:28
  • @ChristianHackl: It certainly did not. And you don't think C++ is insanely complex even when considering otherwise simple tasks? – Lightness Races in Orbit Aug 09 '15 at 16:30
  • 1
    @LightnessRacesinOrbit: It's certainly insanely complex to implement, not so much to *use* it, at least 99% of the time. I think C++ is considered a complex beast mainly because (1) it's often used incorrectly as C with classes and manual memory management and (2) it's closer to the system than other languages, which naturally exposes you to more system-related problems. Technicalities of struct typedefs are interesting from a language POV but certainly don't keep programmers from getting the job done (point in case: the OP had already solved the problem). – Christian Hackl Aug 09 '15 at 16:38
  • @ChristianHackl: I disagree. You have to be a genius to get move semantics and TMP right. It's not at all expressive and it's full of hacks/oddities. – Lightness Races in Orbit Aug 09 '15 at 16:46
  • @LightnessRacesinOrbit: I certainly feel that C++ is a bit complex but it is really fast. The thing is that I don't understand the example where typedef int I; is repeated twice, what is the use of that bit of code. I know that using typedef in C++ is moot but I was reading the differences between C and C++ and this seemed interesting and we couldn't figure it out, awesome explanation! – Achyut Rastogi Aug 10 '15 at 18:43
  • @AchyutRastogi: Using `typedef` in C++ is not moot. The example is contrived: it serves no "use"; it's just demonstrating that particular rule of the language. – Lightness Races in Orbit Aug 10 '15 at 19:20
5

The C example you gave should be an error. You're using a tag name (node) that you haven't defined with struct node.

Given those two choices, the second is the one to use. I prefer a bit of economy:

typedef struct node_t
{
    int data;
    struct node_t *next;
} node_t;

In C or C++, the tag names have their own namespace, so there is no problem with using the same name for the tag and the typedef name. In C, this allows you to use either node_t or struct node_t to refer to this struct type. C++ will search the tag names for a type name if a declared type name doesn't exist, so the above double-definition isn't needed, but doesn't hurt.

In both languages, the explicit struct node_t version is required at any point before the type is completely defined, so any self-reference, and any forward references will use the struct version. I prefer this in header files, mostly because it reduces problems with order of #include directives.

PS: This does work in either language (see LRIO's answer for pointers into the C++11 Standard) and has been used in enough bilingual and even pure C++ header files that it's unlikely to disappear soon) so it's a very simple approach that just works in either language.

Mike Housky
  • 3,959
  • 1
  • 17
  • 31
  • An unnamed struct typedef would be better (in both languages) `typedef struct {} my_struct_t;`, wouldn't it? I don't know.. whats your view on that? Any disadvantages? – nonsensation Aug 09 '15 at 12:22
  • @Serthy AFAIK, you can't use forward references in C to get to refer to that `my_struct_t` type, unless you have a tag name. `struct my_struct_t *ptr` won't work, and `my_struct_t *ptr` won't work if the full declaration of `my_struct_t` hasn't been seen. If the use and definition are in separate header files, you have an order-of-inclusion problem. If the references are circular and in separate headers, you have an *insoluble* include-order problem. – Mike Housky Aug 09 '15 at 12:33
  • 2
    The opening sentence is incorrect; the code is perfectly valid, but very unorthodox, C — and is indubitably not what was intended. The `typedef` statement introduces three type names; `struct nodes` (which is complete by the end of the statement), `node` (a synonym for `struct nodes`), and an incomplete type `struct node`. This incomplete type is separate from `struct nodes`; the `struct nodes` type simply contains a pointer to this other type. You cannot create a list of `nodes` using the `next` pointer without abusive casts; a `struct node` is distinct from a `struct nodes`. – Jonathan Leffler Aug 09 '15 at 15:09
  • 5
    Also note that POSIX [reserves words ending `_t`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_02_02) for use (as type names); it is not necessarily a good idea to use the convention in your own code. – Jonathan Leffler Aug 09 '15 at 15:11
  • @JonathanLeffler Upvote for the POSIX info. I didn't know that...but POSIX isn't standard C, even if they are the last big-time user. As for the first statement, I'm disappointed in both C and C++ in that instantiation of a struct containing a pointer to an incomplete type is apparently allowed. – Mike Housky Aug 09 '15 at 16:11