C: Generic ADT error when passing function pointers

300 Views Asked by At

I have a generic set of elements provided as a library.

/** Type for defining the set */
typedef struct Set_t *Set;

/** Element data type for set container */
typedef void* SetElement;

/** Type of function for copying an element of the set */
typedef SetElement(*copySetElements)(SetElement);

To create the set I must provide a pointer to a function that handles the copying of elements that I intend to use the set for.

Set setCreate(copySetElements copyElement);

I wrote the following type and copy function:

typedef struct location_t {
    char *name;
} *Location;

Location locationCopy(Location location){

    Location new_location = locationCreate(location->name);

    return new_location;
}

*Obviously I simplified everything to focus the discussion.

When I call:

Set locations = setCreate(locationCopy);

I get a compiler errors:

warning: passing argument 1 of ‘setCreate’ from incompatible pointer type

expected ‘copySetElements’ but argument is of type ‘struct location_t * (*)(struct location_t *)’

1

There are 1 best solutions below

1
On

Your example can be boiled down to

typedef void * (*VF)(void *);
typedef int * (*IF)(int *);

IF a = 0;
VF b = a; //Warning

In the C standard pointers to functions are less versatile than pointer to objects.
You can convert a pointer to an object to a pointer to void (and back) without any cast (and warnings) because there is clause in the standard that explicitly allows for it

A pointer to void may be converted to or from a pointer to any object type.
A pointer to any object type may be converted to a pointer to void and back again;
the result shall compare equal to the original pointer.

Note here that a function is not an object.

Regarding pointers to functions the only thing that the standard guarantees is:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer.
If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined

Later the standard clarifies what does it mean for two functions to be compatible:

For two function types to be compatible, both shall specify compatible return types.
Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types.

Now you may think that void* and struct location_t* are compatible types, after all you can assign each others.
But the standard is crystal clear:

Two types have compatible type if their types are the same.

It later goes on with extending this relationship for more complex types and qualified types.

It may sound surprising but int* and void* are not compatible.
They can be assigned but are not compatible, after all the void* could point to a float or an object with different alignment requirements.

When it comes to pointers of different types the standard is concerned mostly about assigning them back and forth.
It doesn't forbid converting between incompatible pointer types, but using them is undefined behavior, hence the warning.


The better approach is to perform the cast inside the function, as suggested in this answer.

All your function should have signature void* (void*) so that no cast between pointers to functions is needed.

SetElement locationCopy(SetElement element)
{
    Location location = (Location)element;

    Location new_location = locationCreate(location->name);

    return new_location;
}

The casts inside the functions are again subject to undefined behavior if the SetElement pointers are cast to incompatible pointers but that shouldn't happen if you'll call locationCopy only with pointers to Locations (as you indeed expect to do).


As a side note, some programmer may frown upon using a typedef to hide a pointer type.