6

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:

  • Offer a workaround that will appease Visual Studio, or
  • Suggest an alternative strategy to achieve similar ends

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:

  • Linux development: Centos 7, g++ (GCC) 4.8.3 20140911
  • Mac development: Apple LLVM version 6.1.0 (clang-602.0.49)
  • Windows development: Visual Studio Express 2013 Update 5 CTP
Community
  • 1
  • 1
duncan
  • 2,323
  • 3
  • 17
  • 21
  • Could you show the specific code that gives that error? – Useless Apr 28 '15 at 13:14
  • 2
    Oh, and is there a specific reason you can't just use `std::function` and get all this stuff for free? That came in C++11, so hopefully VS 2013 has support? – Useless Apr 28 '15 at 13:15
  • @Useless: I've editted the question to draw attention to the line of code in the example application that causes Visual Studio to fail. – duncan Apr 28 '15 at 13:49
  • @Useless: how can I use std::function to achieve what I'm achieving with the template that accepts member function as a parameter? My aim is succinctly build up a C interface to any C++ object an application or plugin writer creates with as little boilerplate repetition as possible. – duncan Apr 28 '15 at 13:53
  • Interesting. Gonna work out a solution. Working... – Lingxi Apr 28 '15 at 14:05
  • Edited code, removing superfluous std::forward, as per Lingxi's observation. – duncan Apr 30 '15 at 14:20

1 Answers1

2

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;
}
Lingxi
  • 14,579
  • 2
  • 37
  • 93
  • Thanks for the valiant effort Lingxi, but I don't think this works when the C++ class has two functions with different names but the same signature because your template class `c_interface_gen` takes only the return and parameter types as parameters, not the function itself. Suppose `Greeter` has an extra function `void farewell(const char * recipient) const`). When you wrap that, the static variable `s_fn_` in the *same* instantiation of the template class is overwritten resulting in a call to `greet_cb()` calling the member function `farewell()`! You're right about `std::forward` -- thanks. – duncan Apr 29 '15 at 09:30
  • @duncan You'are right. I failed miserably to catch this point. Gonna update the answer. – Lingxi Apr 29 '15 at 09:33
  • @duncan Updated. Hope it solves your problem now :-) – Lingxi Apr 29 '15 at 10:58
  • Thanks Lingxi. That's fantastic -- By using a templated static function within the templated class it removes the need for template specialisation (which was Visual Studio's weakness) whilst overcoming the problem of needing the variadic template (Args) parameter to be defined before the member function type. Now I see it, it's so simple (as most good solutions are). I actually like your solution better than my original version -- it's cleaner. I'll accept your answer tomorrow, at which point I can award some bounty points: you really got me out of a jam! – duncan Apr 29 '15 at 12:03
  • @duncan I'm glad my solution helps. Also check out the updated answer. I just added an alternative and improved solution for your consideration :-) – Lingxi Apr 29 '15 at 15:20