简体   繁体   中英

How to pass a member function which has variable arguments as template argument?

I want to write an adapter which can convert non static member functions to C-style function pointers. Here is what I got now(see the following code snippet), but the current solution is not general. I want to make int (T::*Func)(int) accept variable arguments.

Also it's necessary to make CObjectT::f and StoreVals::display have the same signature.

The final goal is Interfacing C++ member functions with C libraries.

class StoreVals
{
    int val;
public:
    int display(int k) { cout << k << endl; return 0; }
};

template<class T, int (T::*Func)(int)>
class CObjectT
{
public:
    /*
     * The signagure of 'f(...)' should change by the argument of template.
     * They must be the same, but i don't know how to achieve this goal.
     */
    static int f(int i)
    { 
        T obj;
        return (obj.*Func)(i);
    }
};

void main()
{
    CObjectT<StoreVals, &StoreVals::display>::f(7);
    auto function_t = &CObjectT<StoreVals, &StoreVals::display>::f;

    // Now it's a C-style function pointer
    cout << typeid(function_t).name() << endl;
}

I don't think it is possible to dynamically change the name of a function based on a template parameter, but you can change the arguments/return type based on the template parameter. It does require some extra type information in the template declaration but it does allow what you want.

#include <iostream>
#include <utility>

template< typename T, T t >
class Delegate;


template< typename R, typename C, typename... Args, R ( C::*F ) ( Args... )>
class Delegate< R ( C::*)( Args... ), F > {
public:
    template< typename... Ts >
    static R invoke( Ts&&... args ) {
        C t;
        return ( t.*F )( std::forward< Ts >( args )... );
    }

};

template< typename R, typename... Args, R ( *F ) ( Args... ) >
class Delegate< R ( * ) ( Args... ), F > {
public:
    template< typename... Ts >
    static R invoke( Ts&&... args ) {
        return F( std::forward< Ts >( args )... );
    }
};

void print( int v ) {
    std::cout << "Static: " << v << std::endl;
}

class Class {

    void print( int v ) {
        std::cout << "Class: " << v << std::endl;
    }

};

int main( int argc, char** argv ) {
    Delegate< void ( * )( int ), &print >::invoke( 1 );
    Delegate< void ( Class::* ) ( int ), &Class::print >::invoke( 1 );

    return 0;
}

Output:

Static: 1
Class: 1

This does use C++11's variadic templates and Rvalue References for perfect forwarding. That is why you see the weird std::forward< Args >( args )... in the function calls.

This doesn't work with variadic parameter functions such as printf and the like. It might be possible but would require a lot more template black magic that I don't have time to write and test.

If you want to interface with C, you cannot use templates and you cannot use member functions (even static). The only way is a honest hand-written extern "C" function. Templates or member functions cannot have C linkage.

If you want to sacrifice portability for convenience you can do something like this:

#define TYPE_AND_VALUE(x) decltype(x),x

#define MemFuncTypeAdapter(x) MemFuncTypeAdapterStruct<TYPE_AND_VALUE(x)>

extern "C" 
{
    int (*cfunc)(struct A*, int);
}

template <typename MemFuncType> struct MemFuncTypes;
template <typename Class, typename Ret, typename... Args> 
struct MemFuncTypes<Ret (Class::*)(Args...)>
{
    using RetType = Ret;
    using ClassType = Class;
};

template <typename MemFuncType, MemFuncType memFunc>
struct MemFuncTypeAdapterStruct
{
    using RetType = typename MemFuncTypes<MemFuncType>::RetType;
    using ClassType = typename MemFuncTypes<MemFuncType>::ClassType;

    template <typename... Args>
    static RetType func (ClassType* c, Args... args) 
    {
        return (c->*memFunc)(args...);
    }
};

struct A
{
    A() : a(33) {};
    int a;
    int plus (int b) { return a + b; }
};

int main ()
{
    MemFuncTypeAdapter(&A::plus) aplus;
    A a;
    aplus.func(&a, 22);

    cfunc = &MemFuncTypeAdapter(&A::plus)::func; //<- C interface here

}

Notes.

  1. The adapter has an additional Class* argument. See comments for an explanation of this.
  2. Don't bother with perfect forwarding. You are passing C compatible types in there anyway. Which means scalars and pointers only.
  3. The ugly macro is necessary to avoid repeating typename(x), x every time. Hopefully a future C++ standard will deal with it.
  4. Parts of this can probably be found in the standard library, pointers are welcome.

Demo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM