I guess that's where we drift apart. I tend to view the names as the
interesting part, and the question of whether two distinct names happen
to be interchangeable under the hood as relatively trivial, because, as
noted, it can happen purely by accident.
So I find it more useful, for the most part, to treat the names as being
the Real Thing, and the things to which they correspond, which may or may
not be distinct, as a sort of accident-of-circumstance. So far as I'm
concerned, uid_t and gid_t are two different types, and it really doesn't
matter to me at all that, on some systems, they could be viewed as "two
names for a single type". It's useful to be aware that, in fact, many types
cannot be distinguished from each other -- but I find that I write better
code when I ignore that in favor of viewing them as distinct regardless.
Ok. Now we can get somewhere. Let's consider the follow C code:
struct aa { int x; };
struct bb { int x; };
typedef struct aa cc;
typedef cc dd;
typedef struct bb ee;
typedef ee ff;
void aa_func(struct aa* );
int main()
{
struct aa aa_var;
struct bb bb_var;
cc cc_var;
dd dd_var;
ee ee_var;
ff ff_var;
aa_func(&aa_var); /*allowed*/
aa_func(&bb_var); /*compiler failure, incompatible types*/
aa_func(&cc_var); /*allowed*/
aa_func(&dd_var); /*allowed*/
aa_func(&ee_var); /*compiler failure, incompatible types*/
aa_func(&ff_var); /*compiler failure, incompatible types*/
}
You are welcome to think about it however you want, but your usage of
terms is wrong - it is inconsistent with how those terms are used by
programmers at large. Please desist.
Type has many meanings in the programming world, with many
formalizations, but one very important meaning is with regards to the
compiler and the type system, specifically the C (static aka compile-
time) type checker.
In the above example, the lines in main marked as compiler failure
will produce a compiler failure. They will produce a compiler failure
because there is no conversion from the argument type to the parameter
type. They are different types.
For the lines marked as /*allowed*/, the argument type and the
parameter type are consistent, so there is no compiler error. Looking
over the implicit conversion rules, we can see that there is no
implicit conversion happening here. They are the same type.
The usage of the term "define a type" in the programming world at
large, and in the C community, means "to define a /new/ type" or
"introduce a /new/ type". Specifically, the just-introduced type must
be distinct from all other types (in that translation unit) - distinct
in the sense that the compiler and type checker will tell you that the
types are distinct. For the set of type names { "struct aa", "struct
bb", "cc", "dd", "ee", "ff" }, there are 6 type names, but those type
names refer only to 2 types. Typedef does not define types.
The fact that the C standard puts "typedef" under the very large
header of "type definitions" is an oversight. You're reading far too
much into that. To the contrary, the wording in the section describing
typedef talks about defining names but not defining types.
"So I find it more useful, for the most part, to treat the names as
being the Real Thing"
Unfortunately, that line of thinking is broken here. It's a useful
shorthand when there is little possibility for confusion, but now is
not such a case. The name of a thing is not the thing itself, and with
regards to types and type names, the name of a type is not the type
itself.
From Shakespeare's Romeo and Juliet, 1600: