Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrapping C++ member function in C - Visual Studio 2013 template woes

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
like image 621
duncan Avatar asked Apr 28 '15 12:04

duncan


1 Answers

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;
}
like image 125
Lingxi Avatar answered Oct 21 '22 22:10

Lingxi