2017-09-13

Overloading reloaded (C)

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 typedefined to the same struct, namely a struct which is “structurally” the same, so to speak. Nonetheless C doesn't delve into the structs 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 typedefs 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 typedefined 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