In Overloading so much I've forgotten C11 and its _Generic
I've talked about in Type-generic functions (overloading) in C11.
In C we haven't a primitive string
type and we must use char *
or similar.
The straightforward implementation would be:
// ...
typedef char *Name;
typedef char *Surname;
//...
#define load(Y, X) _Generic((X), \
Name: load_by_name, \
Surname: load_by_surname)((Y), (X))
// ... (see below)
This doesn't compile:
... other errors ...
c11.c:16:29: error: ‘_Generic> selector matches multiple associations
Surname: load_by_surname)((Y), (X))
...
But we are more lucky with this approach:
typedef struct { const char *str; } Name;
typedef struct { const char *str; } Surname;
// #define load ... here, as given above
void load_by_name(void *d, const Name n)
{
printf("Load by name: %s\n", n.str);
}
void load_by_surname(void *d, const Surname n)
{
printf("Load by surname: %s\n", n.str);
}
This works! Name
and Surname
are typedef
ined to the same struct
, namely a struct
which is “structurally” the same, so to speak. Nonetheless C doesn't delve into the struct
s to see that they are the same, and so it treats them as a different type — it is like if internally anonymous structures get different names, no matter if they are the same, hence they are seen like different types as it would be for, e.g., struct M1 { ... }
and struct M2 { ... }
.
This means that saying that typedef X Y;
defines a synonym so that you can always replace Y with X isn't totally correct.
As a result of using an anonymous structure, the compiler distinguishes between Name
and Surname
.
An usage example:
int main(void)
{
unsigned char x[256];
Name a_name = {"Gerry"};
Surname a_surname = {"Keith"};
load(x, a_name); // prints "Load by name: Gerry"
load(x, a_surname); // prints "Load by surname: Keith"
return 0;
}
Can we make the typedef
s more interesting? No luck: the compilation of the following excerpt (injected into its life support) won't succeed.
typedef struct
{
const char *str;
} string;
typedef string Name;
typedef string Surname;
In this case the string
“fixes” the same (anonymous) structure, which will be used in both Name
and Surname
, hence the compiler will see them as being synonyms for the same type string
.
It is equivalent to:
struct string // not anonymous anymore
{
const char *str;
};
typedef struct string Name;
typedef struct string Surname;
While anonymous structures can be replaced with named ones.
struct string0
{
cont char *str;
};
struct string1
{
cont char *str;
};
typedef string0 Name;
typedef string1 Surname;
But of course this doesn't make sense.
Anonymous structures are the way to trick the compiler…
If we have a library which provides a nice string
type, very likely we don't want to have to type
typedef struct { /* very complex */ } Name;
and in the same time we can't use
typedef struct { /* very complex */ } string;
But we can still “wrap” a struct into a struct…
typedef struct { /* very complex */ } *string;
//...
typedef struct { string str; } Name;
typedef struct { string str; } Surname;
And later we'll use it like this:
void load_by_name(void *d, const Name n)
{
printf("Load by name: %s\n", n.str->c_str);
}
//...
int main(void)
{
// ...
Name a_name = { string_new("Gerry") };
// ...
load(a_name);
// ...
string_free(a_name.str);
return 0;
}
It is all very cumbersome, but it works. If we are going to use the imagined library functions string_*
to manipulate strings before needing to call load
, likely we prefer to wrap it only at the end, when we need to call load
. This is the same we had in C++ or Java: we wrap the string in the very moment we need to call specific load
.
In this case instead of calling directly it
string n = string_new("Gerry2");
// ...
load_by_name(x, n);
// ...
string_free(n);
we want to rely on _Generic
, hence we write
string n = string_new("Gerry2");
// ...
load(x, (Name){n});
// ...
string_free(n);
Compare it with C++ (see Overloading so much):
o.load(Name(the_name));
Also in C, we “wrap” the string
into the anonymous typedef
ined struct Name
just to call the right function, load_by_name
in this example.
All these efforts just to avoid writing load_by_X
… they don't seem to be worth it.
No comments:
Post a Comment