C++ API Design – Idiomatic Wrapping of C++ Template Type API in C

api-designc

I'm working on wrapping a C++ API which provides access to a data store (Hazelcast) in C functions, so that the data store can also be accessed from C-only code.

The Hazelcast C++ API for the Map datastructure looks like this:

auto map = hazelcastClient->client->getMap<int, string>(mapName);
map.put(key, value);

It makes use of template types for key and value parameters. As there are no templates available in C, i thought about creating a wrapper function for each specialization of the getMap<T, U> method. That is, for each C type. Although I'm aware that there are signed and unsigned versions of C types, I'm fine with limiting the API to support only int, double, float, char * for key and value.

So I wrote a small script, that auto-generates all combinations. The exported functions look like this:

int Hazelcast_Map_put_int_string(
    Hazelcast_Client_t *hazelcastClient,
    const char *mapName,
    int key,
    char *value,
    char** errptr
);

int Hazelcast_Map_put_int_int(
    Hazelcast_Client_t *hazelcastClient,
    const char *mapName,
    int key,
    int value,
    char** errptr
);

...

Generating a function for get, set, contains with all possible combinations of key and value types increases the amount of code quite a lot, and although I think generating the code is a good idea, it adds additional complexity by having to create some kind of code-generating infrastructure.

Another idea I can imagine is one generic function in C, like this:

int Hazelcast_Map_put(
    Hazelcast_Client_t *hazelcastClient,
    const char *mapName,

    const void *key,
    API_TYPE key_type,

    const void *value,
    API_TYPE value_type,

    char** errptr
);

Which can be used like this:

Hazelcast_Map_put(client, mapName, "key", API_TYPE_STR, "val", API_TYPE_STR, &err);

This makes it a bit easier for the caller, because it shifts the burden of getting the correct specialization on my code, but it looses type safety and requires casts. Also, for passing in an int, as void * is now the type of key and value, a cast like (void *) (intptr_t) intVal would be needed on the callers side, which again isn't super nice to read and maintain.

  • Is there any third option, which I can't recognize?
  • Which version would be preferred by C developers?

I'm mostly inclined to auto-generate all type combinations and create a function for each, although the header file will become quite huge I suppose.

Best Answer

Generating for all possibilities did not look to be a very good solution to me. The key and values may be objects as well. Hence, the possibilities are infinite :(

Did you have a look at IMapImpl class? This class does not use types but the binary data (which is provided after serialization). Hence, another solution would be writing an API that mimics this interface + providing a Serialization utility which converts any given type to the binary that this interface needs.

E.g.

API:

struct Binary {
   byte *data;
   size_t length;
   int32_t dataType;
};
Binary *hazelcast_map_put(const Binary *key, const Binary *value);

Serialization utility:

int hazelcast_binary_to_int(const Binary *data);

You may need to write these helper functions for object types that you would like to support. This may be a viable interface. There are things to be considered such as memory management.

Serialization is a complex subject, but you can surely start with supporting the primitive types first. See http://docs.hazelcast.org/docs/3.6/manual/html-single/index.html#serialization and https://github.com/hazelcast/hazelcast/blob/master/hazelcast/src/main/java/com/hazelcast/internal/serialization/impl/ConstantSerializers.java for serialization details.