I am making generic wrapper of C++ class into C struct with function table. C function table is a part of library public interface and I have no control over it. Some functions can be set to NULL and may have special behavior that can't be achieved with non-null function implementation.
I am attempting to make template function that automatically generate C function table from arbitrary C++ class. If C++ class have no method that correspond to function from C function table, NULL should be set to table entry. How to detect presence of C++ class method and switch between lambda wrapper over method and NULL?
C interface:
struct device_module_info {
module_info info;
status_t (*init_device)(void *driverCookie, void **_deviceCookie);
void (*uninit_device)(void *deviceCookie);
void (*device_removed)(void *deviceCookie);
status_t (*open)(void *deviceCookie, const char *path, int openMode,
void **_cookie);
status_t (*close)(void *cookie);
status_t (*free)(void *cookie);
status_t (*read)(void *cookie, off_t pos, void *buffer, size_t *_length);
status_t (*write)(void *cookie, off_t pos, const void *buffer,
size_t *_length);
status_t (*io)(void *cookie, io_request *request);
status_t (*control)(void *cookie, uint32 op, void *buffer, size_t length);
status_t (*select)(void *cookie, uint8 event, selectsync *sync);
status_t (*deselect)(void *cookie, uint8 event, selectsync *sync);
};
Example C++ class to be wrapped:
class DeviceHandleImpl: public ImplBase<DeviceHandle, DeviceHandleImpl, gDeviceImplClass> {
public:
status_t Close();
status_t Free();
status_t Read(off_t pos, void *buffer, size_t *_length);
status_t Write(off_t pos, const void *buffer, size_t *_length);
status_t IO(io_request *request);
status_t Control(uint32 op, void *buffer, size_t length);
status_t Select(uint8 event, selectsync *sync);
status_t Deselect(uint8 event, selectsync *sync);
};
device_module_info gDeviceImplClass = GetDeviceClass<
gDeviceImplClass, gDriverImplClass, DeviceImpl, DeviceHandleImpl
>({
.name = "testDevice"
});
My current attempt of wrapper generator function:
template <const device_module_info& selfClass, const driver_module_info& driverClass, typename DeviceImpl, typename DeviceHandleImpl>
constexpr device_module_info GetDeviceClass(const module_info& info)
{
const device_module_info cls = {
.info = info,
.init_device = [](void *driverCookie, void **_deviceCookie) {
Device device;
status_t res = DeviceImpl::InitDevice({&driverClass, driverCookie}, device);
*_deviceCookie = device.fInst;
return res;
},
.uninit_device = [](void *deviceCookie) {
return static_cast<DeviceImpl*>(deviceCookie)->UninitDevice();
},
.device_removed = [](void *deviceCookie) {
return static_cast<DeviceImpl*>(deviceCookie)->DeviceRemoved();
},
.open = [](void *deviceCookie, const char *path, int openMode, void **_cookie) {
DeviceHandle handle;
status_t res = static_cast<DeviceImpl*>(deviceCookie)->Open(path, openMode, handle);
*_cookie = handle.fInst;
return res;
},
.close = [](void *cookie) {
return static_cast<DeviceHandleImpl*>(cookie)->Close();
},
.free = [](void *cookie) {
return static_cast<DeviceHandleImpl*>(cookie)->Free();
},
// `DeviceHandleImpl::Read` method may not exist, how to set entry to `nullptr` in this case?
.read = [](void *cookie, off_t pos, void *buffer, size_t *_length) {
return static_cast<DeviceHandleImpl*>(cookie)->Read(pos, buffer, _length);
},
.write = [](void *cookie, off_t pos, const void *buffer, size_t *_length) {
return static_cast<DeviceHandleImpl*>(cookie)->Write(pos, buffer, _length);
},
.io = [](void *cookie, io_request *request) {
return static_cast<DeviceHandleImpl*>(cookie)->IO(request);
},
.control = [](void *cookie, uint32 op, void *buffer, size_t length) {
return static_cast<DeviceHandleImpl*>(cookie)->Control(op, buffer, length);
},
.select = [](void *cookie, uint8 event, selectsync *sync) {
return static_cast<DeviceHandleImpl*>(cookie)->Select(event, sync);
},
.deselect = [](void *cookie, uint8 event, selectsync *sync) {
return static_cast<DeviceHandleImpl*>(cookie)->Deselect(event, sync);
},
};
return cls;
}
There are several ways of doing this using requires
, depending on how strictly you want to check the method.
E.g.:
device_module_info cls{};
if constexpr (requires(DeviceHandleImpl h, off_t pos, void *buffer, size_t *_length){
h.Read(pos, buffer, _length);
})
cls.read = /*some lambda*/;
This only checks that the method exists and that the argument types are correct. If the return type is wrong, you get a compilation error.
You could check the return type too. This checks for exact match:
if constexpr (requires(DeviceHandleImpl h, off_t pos, void *buffer, size_t *_length) {
requires std::same_as<status_t, decltype(h.Read(pos, buffer, _length))>;
})
cls.read = /*some lambda*/;
Or this checks for convertibility:
if constexpr (requires(DeviceHandleImpl h, off_t pos, void *buffer, size_t *_length) {
{ h.Read(pos, buffer, _length) } -> std::convertible_to<status_t>;
})
cls.read = /*some lambda*/;
All of the approaches above allow implicit conversions in arguments. I don't think there's an easy way to prevent that.