All agree in that it is not type substitution, and that it is much better than it when pointers get into the mix, but there are also other subtleties. In particular the use of typedefs can affect how code is parsed and the validity of programs.
Both in C and in C++ user defined identifiers are kept in different name spaces (not in the C++ sense, but some kind of identifier-space). When you use the typedef keyword you make an alias for the type in the global name space, where functions reside.
// C/C++
struct test {};
void test( struct test x ) {} // ok, no collision
// C/C++
typedef struct test {} test; // test is now both in types and global spaces
//void test() {} // compiler error: test is a typedef, cannot be redefined
A slight difference here is that in C++ the compiler will first look in the global name space and if not found there it will also look in the types name space:
// C
struct test {};
//void f( test t ); // error, test is not defined in the global namespace
void f( struct test t ); // ok, we are telling the compiler where to look
// C++
struct test {};
void f( test t ); // ok, no test defined in the global name space,
// the compiler looks in the types name space
void g( struct test t ); // also ok, even if 'struct' not needed here.
But this does not mean that the two namespaces are merged, only that the lookup will search in both places.