In the latest working draft, the reference is [dcl.typedef]/4.
The significance of "for linkage purposes" is given by [basic.link]/8:
Two declarations of entities declare the same entity if, considering declarations of unnamed types to introduce their names for linkage purposes, if any ([dcl.typedef], [dcl.enum]), they correspond ([basic.scope.scope]), have the same target scope that is not a function or template parameter scope, and either
- they appear in the same translation unit, or
- they both declare names with module linkage and are attached to the same module, or
- they both declare names with external linkage.
[Note 2: There are other circumstances in which declarations declare the same entity ([dcl.link], [temp.type], [temp.spec.partial]).
— end note]
So let's consider the following example:
// S1.h
struct S1 {
int member;
};
// A.cpp
#include "S1.h"
extern S1 s1;
// B.cpp
#include "S1.h"
S1 s1;
After preprocessing, A.cpp
and B.cpp
will be as follows:
// A.cpp
struct S1 {
int member;
};
extern S1 s1;
// B.cpp
struct S1 {
int member;
};
S1 s1;
Each translation unit contains the definition of a struct named S1
. Is A.cpp
's S1
the same struct as B.cpp
's S1
? If the answer is yes, then then B.cpp
's definition of the object S1 s1;
links with A.cpp
's declaration extern S1 s1;
. If the answer is no, then the two translation units declare the same variable with different types, and the program is ill-formed (no diagnostic required).
Well, you probably know that we would be in big trouble if B.cpp
's definition of s1
were not a definition of A.cpp
's s1
. It must be the case that both translation units are defining the same type S1
.
[basic.link]/8 tells us that this is indeed so. Both declarations of S1
declare it with external linkage. Both have the same target scope---namely, the global scope. And they correspond because they both introduce the same name; [basic.scope.scope]/4:
Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless [irrelevant exceptions omitted].
So both declarations declare the same type. A.cpp
's S1
is the same type as B.cpp
's S1
. A.cpp
's s1
has the same type as B.cpp
's s1
.
Now let's change the example slightly:
// S2.h
typedef struct {
int member;
} S2;
// C.cpp
#include "S2.h"
extern S2 s2;
// D.cpp
#include "S2.h"
S2 s2;
After preprocessing:
// C.cpp
typedef struct {
int member;
} S2;
extern S2 s2;
// D.cpp
typedef struct {
int member;
} S2;
S2 s2;
Does this work the same way as the other example?
Well, each typedef declaration introduces the typedef name S2
, and [basic.link]/8 tells us that these two declarations declare the same "entity" S2
. But we must keep in mind that the kind of "entity" being declared here is a typedef name, not a type. In other words the program only contains one typedef name known as S2
. But the question is whether both translation units declare S2
to be aliases to the same type. If not, the program is ill-formed (no diagnostic required).
In other words, in order for this program to be correct, it must be that struct { int member }
in C.cpp
declares the same type as struct { int member }
in D.cpp
.
And here, [dcl.typedef]/4 saves us. It tells us that both C.cpp
and D.cpp
, by defining an unnamed struct in a typedef declaration that makes S2
an alias for that struct, give the struct the name S2
"for linkage purposes". Then, when we use [basic.link]/8 to determine whether those two structs are the same struct, we are instructed to consider them to have the names that they have for linkage purposes. This makes them correspond under [basic.scope.scope]/4, so they declare the same type.
If the "for linkage purposes" rule didn't exist, then they wouldn't correspond (because, if neither one has a name, then we can't claim that they both have the same name) and the program would be ill-formed (no diagnostic required).
If you're wondering why anyone would write typedef struct { int member; } S2;
in the first place, the answer is that you probably wouldn't do this in pure C++, but this practice is common in C, and you might be including a C header into your C++ program. So for historical reasons, C++ has to have a special rule to support linking together such declarations.
(But then why does C allow it? It's because C doesn't use "name-based linkage" for types the way that C++ does. It instead uses the concept of "compatible types", which I won't delve into here.)