0

I want to wrap a C library, libcookie, in C++.

Setting

The library has an object-like structure like so

struct Cookie;
typedef struct Cookie COOKIE;

int CookieGetTemperature(COOKIE*);
// ...
COOKIE* CookieGetBest(); // Meant as reference

So I want to wrap it with something like the following:

struct ObjCookie {
    COOKIE* ptr;

    int getTemperature() { return CookieGetTemperature(ptr); }
};

ObjCookie getBest() {
    return {CookieGetBest()};
}

The actual wrapping is not as simple, given return codes, out parameters, etc., which is why I want to provide the wrapping.

Problem

My difficulty comes with arrays of cookies:

COOKIE** CookieGetAll();       // Meant as array of references
int      CookieGetNCookies();  // Size of previous array

I'd like to be able to reinterpret the array as a span (like std::span but third party):

span<ObjCookie> getAll() {
    return {reinterpret_cast<ObjCookie*>(CookieGetAll()), CookieGetNCookies()};
}

But I understand that this is undefined behavior. Is there a way around to make this idea work? Using compiler specific hacks? Or keep UB with a check like static_assert(sizeof(ObjCookie) == sizeof(COOKIE))

Alternatively, I thought of making a copy, but it can be a bit of performance hit if the array is large and the function called often.

Finally the last option would be to not wrap COOKIE in ObjCookie, but only the free functions (CookieGetTemperature).

1
  • If you don't have strong portability requirements and can tolerate a bit of UB, such casts can be acceptable. Yes, documenting and checking your assumptions via a static_assert is an excellent idea.
    – amon
    Commented Apr 10, 2021 at 8:25

1 Answer 1

2

Fly Weight

Wrap the individual COOKIE* in a Cookie class. eg: class Cookie { private: COOKIE* c; };

Then you can very quickly make an array of Cookies and pass that into your span.

Container

Instead of wrapping in a span. Wrap the array itself. You'll probably want to have a fly weight still for item access, just create these on demand.

class Cookies
{
private:
   COOKIE** c;
   unsigned int n;
public:
   Cookie operator[](unsigned int index) const
   {
     if (index >= n) throw std::exception("");
     return Cookie(c[index]);
   }
}

Nit picks

As for why your example code is undefined: reinterpret_cast<ObjCookie*>(CookieGetAll()) what does that even mean???

Do you have super secret knowledge about the layout of COOKIE* and have confirmed that the compiler will generate exactly the same layout for ObjCookie?

If not your playing dice with just so stories. It just so happens that on the 4th day of solarium, when you stick your tongue out, and hold a waxen pear that it works. Now should we take this as holy scripture that this we always be the case, or did it just happen to work once, and all current replications have worked, yet we missed the actually important information?

2
  • Thanks, wrapping the container is indeed what I would need to do. As for the layout, I have experience, but my intuition was that COOKIE* being a literal type, and ObjCookie a trivial one, I would (naively) think it makes sense.
    – AntoinePrv
    Commented Apr 8, 2021 at 1:23
  • Almost, except that you must make sure they have the same size under the hood, which can change based on architecture, platform, and compiler. Otherwise when you pass by value a ObjCookie, you are going to leave behind to much, or take to much along for the ride.
    – Kain0_0
    Commented Apr 8, 2021 at 4:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.