Bjarke Freund-Hansen
Bjarke Freund-Hansen

Reputation: 30128

Using templates to parameterize types in interface mapping a legacy C interface

I have a (legacy) c interface looking something like this:

// c interface
#include <stdint.h>
#include <string.h>

typedef enum {
    type_1 = 0, /* native type: uint8_t */
    type_2 = 1, /* native type: double */
    // ... lots of more types defined ...
} thing_type_t;

void thing_set(thing_type_t type, size_t type_size, void* value);
void thing_get(thing_type_t type, size_t type_size, void* return_value);

void thing_set_type_1(uint8_t value);
void thing_get_type_1(uint8_t *value);

void thing_set_type_2(double value);
void thing_get_type_2(double *value);

// ...

So basically you can set thing, and based on which thing_type_t you choose there is a corresponding native type. This interface I cannot change.

Now I want to do a C++ interface utilising type polymorphism, so I from a client can do something like:

// C++ client
#include <cstdint>

int main(void)
{
    Thing thing;
    thing.set(std::uint8_t(42));
    thing.set(42.0);

    auto uivalue = thing.get<std::uint8_t>();
    auto dvalue = thing.get<double>();

    return 0;
}

It does not have to be exactly like this, but the idea is that the client do not have to worry about the internal thing_type_t types, but just uses the corresponding native types.

What I have come up with is using templates like this:

// C++ interface
#include <cstdint>

class Thing
{
public:
    template <typename T> void set(T value);
    template <typename T> T get();
};

template <> void Thing::set(uint8_t value)
{
    thing_set(type_1, sizeof value, reinterpret_cast<void*>(&value));
}
template <> std::uint8_t Thing::get()
{
    uint8_t ret = 0;
    thing_get(type_1, sizeof ret, &ret);
    return ret;
}

template <> void Thing::set(double value)
{
    thing_set(type_2, sizeof value, reinterpret_cast<void*>(&value));
}
template <> double Thing::get()
{
    double ret = 0;
    thing_get(type_2, sizeof ret, &ret);
    return ret;
}

I.e. I am doing type specialisation for each of the thing_type_t which results in bloated code with lots and lots of repeated code.

How do I simplify this so I can express the mapping between thing_type_t and native type without duplicating the get/set function again and again?

I feel like I should be able somehow parameterize the get and set functions with both the thing_type_t and native type as well as the mapping. But I cannot figure out how to do that.

Online editable example

Upvotes: 3

Views: 59

Answers (2)

Alan Birtles
Alan Birtles

Reputation: 36498

As the only thing that changes between implementations is the first argument I'd just specialise that part:

class Thing
{
public:
    template <typename T> void set(T value)
    {
        thing_set(getType<T>(value), sizeof value, reinterpret_cast<void*>(&value));
    }
    template <typename T> T get()
    {
        T ret = 0;
        thing_get(getType<T>(ret), sizeof ret, &ret);
        return ret;
    }

private:
    template <typename T> thing_type_t getType(T);
};

template <> thing_type_t Thing::getType(uint8_t)
{
    return type_1;
}
template <> thing_type_t Thing::getType(double)
{
    return type_2;
}

Upvotes: 5

Andrew Kashpur
Andrew Kashpur

Reputation: 746

macroses can help in such cases:

typemap.h

macro(type_1, std::uint8_t)

thing.h

class Thing
{
public:
    template <typename T> void set(T value);
    template <typename T> T get();
};

#define macro(thing_type, actual_type) template <> void Thing::set(actual_type value) \
{\
    thing_set(thing_type, sizeof value, reinterpret_cast<void*>(&value));\
}\
#include <typemap.h>
#undef macro 

#define macro(thing_type, actual_type) template <> actual_type Thing::get()\
{\
    actual_type ret = 0;\
    thing_get(thing_type, sizeof ret, &ret);\
    return ret;\
}\
#include <typemap.h>
#undef macro

so in the end you only have to modify typemap.h to add/remove specializations

Upvotes: 0

Related Questions