I'm creating a lightweight cross-platform plugin framework which uses C interfaces between the application and the plugin (both typically, but not always, written in C++).
One of my challenges to aid C++ application and plugin writers is to find a simple way to expose C++ object functionality across a C interface. My present solution feels simple and uses templates to "build" C-signature functions that wrap underlying C++ member functions, based on this great stackoverflow question and answer
template <typename Tc, typename F, F>
struct MemberFuncWrapper;
template <typename Tc, // C interface structure tag
typename T, // C++ class, derived from Tc
typename R, // C++ member function return type
typename ...Args, // C++ member function argument types
R (T::*f)(Args...) const> // C++ member function
struct MemberFuncWrapper<Tc, R (T::*)(Args...) const, f> {
static R call(const Tc * tc, Args... args) {
const T * t = static_cast<const T *>(tc);
return ((*t).*f)(args...);
}
};
Instantiation of this template compiles and runs well under linux (gcc) and mac (clang) but compilation in Visual Studio 2013 fails with:
error C2440: 'specialization' : cannot convert from 'overloaded-function' to 'void (__cdecl Greeter::* )(void) const'
error C2973: 'MemberFuncWrapper<Tc,R(__cdecl T::* )(Args...) const,f>' : invalid template argument 'overloaded-function'
The standalone example code below shows the line where Visual Studio fails (in the Greeter
class definition). I'm hoping someone can either:
Here's standalone code that demonstrates the template code used in the context of implementing a C interface using a C++ class in a rather verbose Hello world
application:
#include <iostream>
#include <utility>
//
// C interface and function(s) typically defined elsewhere
//
#ifdef __cplusplus
extern "C" {
#endif
// The C interface implemented by a 'greeter'
struct greeter_c {
void(*greet_cb)(const struct greeter_c * greeter,
const char * recipient);
};
// Some C function that makes use of a greeter
void broadcast(const struct greeter_c * greeter) {
greeter->greet_cb(greeter, "world");
}
#ifdef __cplusplus
} // extern "C"
#endif
//
// Template magic that envelopes a C++ member
// function call in a C-signature function
//
template <typename Tc, typename F, F>
struct MemberFuncWrapper;
template <typename Tc, // C interface structure tag
typename T, // C++ class, derived from Tc
typename R, // C++ member function return type
typename ...Args, // C++ member function argument types
R (T::*f)(Args...) const> // C++ member function
struct MemberFuncWrapper<Tc, R (T::*)(Args...) const, f> {
static R call(const Tc * tc, Args... args) {
// Cast C structure to C++ object
const T * t = static_cast<const T *>(tc);
// Details such as catching/handling exceptions omitted.
// Call C++ member function
return ((*t).*f)(args...);
}
};
// Repeat of the above for non-const member functions omitted
//
// A C++ class that implements the C 'greeter' interface
//
class Greeter : public greeter_c {
public:
// Constructor
Greeter(const char * greeting) : m_greeting(greeting) {
// Set up C interface callback by wrapping member function
// !! The following line causes the Visual Studio compilation error !!
greet_cb = MemberFuncWrapper<greeter_c,
void (Greeter::*)(const char *) const,
&Greeter::greet>::call;
}
// C++ member function that 'does' the greeting
void greet(const char * recipient) const {
std::cout << m_greeting << " " << recipient << std::endl;
}
private:
const char * m_greeting;
};
// An application that greets using a Greeter's C interface
int main(int argc, char * argv[]) {
// Create C++ object that implements C interface
Greeter a("Hello");
// Greet using Greeter's C interface
broadcast(&a);
return 0;
}
Technical details:
Foreword: std::forward
is useless here, as Args...
are explicitly specified. In other words, the instantiated C interface is no longer a template. std::forward
is no use in non-template code. For this reason, std::forward
is not used in the following solutions.
Version 1:
template <typename Base, typename Derived, typename R, typename... Args>
struct c_interface_gen {
template <R(Derived::*mem_fn)(Args...)> inline
static R invoke(Base* pb, Args... args) {
return (static_cast<Derived*>(pb)->*mem_fn)(args...);
}
template <R(Derived::*mem_fn)(Args...) const> inline
static R invoke(const Base* pb, Args... args) {
return (static_cast<const Derived*>(pb)->*mem_fn)(args...);
}
};
This version works. But it is by no means elegant. The main issue lies in the lengthy and non-intuitive syntax of using the facility.
Version 2:
template <typename Sig>
struct mem_fn_sig;
template <typename R, typename D, typename... Args>
struct mem_fn_sig<R(D::*)(Args...)> {
template <R(D::*mem_fn)(Args...)>
struct mem_fn_inst {
template <typename Base>
struct base {
inline static R invoke(Base* pb, Args... args) {
return (static_cast<D*>(pb)->*mem_fn)(args...);
}
};
};
};
template <typename R, typename D, typename... Args>
struct mem_fn_sig<R(D::*)(Args...) const> {
template <R(D::*mem_fn)(Args...) const>
struct mem_fn_inst {
template <typename Base>
struct base {
inline static R invoke(const Base* pb, Args... args) {
return (static_cast<const D*>(pb)->*mem_fn)(args...);
}
};
};
};
template <typename Sig, Sig inst, typename Base>
struct c_interface_gen:
mem_fn_sig<Sig>:: template mem_fn_inst<inst>:: template base<Base>
{};
Obviously, this version is more code than the previous one. But the good point is that the syntax of using the facility is simple and intuitive. In fact, the syntax resembles that of your original facility. I just added some code to make the compilation process easier for MSVC.
Typically, you will use the facility like this:
... = c_interface_gen<decltype(&Derived::f), &Derived::f, Base>::invoke;
If Derived::f
is overloaded, you will have to explicitly specify its type like this:
... = c_interface_gen<void(Derived::*)() const, &Derived::f, Base>::invoke;
Note that, there is no need to specify const Base
here for the const
member function. You just specify the base type. The templates will figure out automatically whether the const
modifier should be added or not.
Following is your example code using this second version:
#include <iostream>
template <typename Sig>
struct mem_fn_sig;
template <typename R, typename D, typename... Args>
struct mem_fn_sig<R(D::*)(Args...)> {
template <R(D::*mem_fn)(Args...)>
struct mem_fn_inst {
template <typename Base>
struct base {
inline static R invoke(Base* pb, Args... args) {
return (static_cast<D*>(pb)->*mem_fn)(args...);
}
};
};
};
template <typename R, typename D, typename... Args>
struct mem_fn_sig<R(D::*)(Args...) const> {
template <R(D::*mem_fn)(Args...) const>
struct mem_fn_inst {
template <typename Base>
struct base {
inline static R invoke(const Base* pb, Args... args) {
return (static_cast<const D*>(pb)->*mem_fn)(args...);
}
};
};
};
template <typename Sig, Sig inst, typename Base>
struct c_interface_gen:
mem_fn_sig<Sig>:: template mem_fn_inst<inst>:: template base<Base>
{};
//
// C interface and function(s) typically defined elsewhere
//
#ifdef __cplusplus
extern "C" {
#endif
// The C interface implemented by a 'greeter'
struct greeter_c {
void(*greet_cb)(const struct greeter_c * greeter,
const char * recipient);
};
// Some C function that makes use of a greeter
void broadcast(const struct greeter_c * greeter) {
greeter->greet_cb(greeter, "world");
}
#ifdef __cplusplus
} // extern "C"
#endif
//
// A C++ class that implements the C 'greeter' interface
//
class Greeter : public greeter_c {
public:
// Constructor
Greeter(const char * greeting) : m_greeting(greeting) {
// Set up C interface callback by wrapping member function
// !! The following line causes the Visual Studio compilation error !!
greet_cb = c_interface_gen<decltype(&Greeter::greet), &Greeter::greet, greeter_c>::invoke;
}
// C++ member function that 'does' the greeting
void greet(const char * recipient) const {
std::cout << m_greeting << " " << recipient << std::endl;
}
private:
const char * m_greeting;
};
// An application that greets using a Greeter's C interface
int main(int argc, char * argv[]) {
// Create C++ object that implements C interface
Greeter a("Hello");
// Greet using Greeter's C interface
broadcast(static_cast<const greeter_c *>(&a));
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With