views:

572

answers:

4

Hi, In a C lib, there is a function waiting a function pointer such that:

lasvm_kcache_t* lasvm_kcache_create(lasvm_kernel_t kernelfunc, void *closure)

where lasvm_kernel_t is defined as:

typedef double (*lasvm_kernel_t)(int i, int j, void* closure);

Now, if I send a method defined in a class to lasvm_kcache_create:

double cls_lasvm::kernel(int i, int j, void *kparam)
...
lasvm_kcache_t *kcache=lasvm_kcache_create(&kernel, NULL);

I get: "cannot convert ‘double (cls_lasvm::)(int, int, void)’ to ‘double ()(int, int, void)’"

What should I do?

A: 

see the C++ FAQ lite

[33] [Pointers to member functions][1]

http://www.parashift.com/c++-faq-lite/pointers-to-members.html

Alon
A: 

Every C++ member function has an implicit, hidden, first parameter, this.

So the method double cls_lasvm::kernel(int i, int j, void* kparam) is really: double cls_lasvm::kernel(cls_lasvm* this, int i, int j, void* kparam), and it is inappropriate/impossible to use it as a function-pointer parameter.

To make progress, convert your method to be a static-member-method. That will remove the this pointer. You may still have other issues to overcome, but that is a good start.

abelenky
I don't think it has an implicit hidden "first parameter": you might just as well say it has an implicit hidden "last parameter". The value `this` is available in the callee, that's all. So it isn't "really" that thing you said - try changing `kernelfunc` to that type, and the code still won't compile. pointer-to-function and pointer-to-member-function are different things.
Steve Jessop
@Steve: I think it's usually defined as the first parameter, actually. This distinction does make a difference, since other functions (and other languages, I suppose) need to know in which order to pass these parameters.
Edan Maor
__Don't__ use static member method for C callback methods. It is not guaranteed to work as the C++ standard deliberately does not specify an ABI.
Martin York
@Steve: granted, the explanation isn’t 100% accurate but it is a good simplification and corresponds to (almost?) every real implementation of member function signatures.
Konrad Rudolph
@Konrad: I agree with Steve. It's too big a simplification; it hints at an equivalence that isn't really there in practice. Visual C++ (32-bit), which predominantly uses the stack for parameter passing, uses a register for the `this` pointer for non-static member functions. This means it isn't an extra parameter either at the beginning or the or the end of the parameter list, it's just an extra parameter. This also means that there's no way to cast it to a static or non-member function and be able to call it correctly.
Charles Bailey
Is the suggestion to *cast* a method? I suppose you can just use a wrapper: `void method_wrapper(int i, int j, void* obj) { reinterpret_cast<X*>(obj)->method(i, j);}` and use the void pointers to sneak in the class instance.
UncleBens
A: 

If it is an external C library whose code you can not modify, then there is not much you can do about it. You'll not be able to call the member function as they require this pointer to work properly (to get the attributes of the object). The easiest workaround, I can think of is using third void* param to pass around this pointer. You can define struct like after defining one more typedef like:

typedef double (cls_lasvm::*lasvm_kernel_t_member)(int i, int j, void* closure);


struct MyParam
{
   A* pThis;
   lasvm_kernel_t_member pMemFun;
   void* kParam;
};

I haven't compiled it, I hope it makes sense.

Then in your class define a static method which receives the call from library:

class cls_lasvm
{
  static double test(int i, int j, void *kparam)
  {
    MyParam* pParam = reinterpret_cast<MyParam*>(kparam);
    return (pParam->*pMemFun)(i,j,pParam->kParam);
  }
};

While calling you should use something like:

cls_lasvm a;
MyParam param;
param.pThis = &a;
param.pMemFun = &cls_lasvm::kernel;
param.kParam = NULL;

lasvm_kcache_create(&cls_lasvm::test,&a);
Ponting
+2  A: 

I'm assuming that the closure argument is a context 'cookie' for the use of the callback to get appropriate context. This is a acomon idiom for callback functions, and seems to be what's going on based on the snippets you've provided (but I don't know for sure, as I don't know anything about kcache_create() except what you posted here).

You can use that cookie to pass a pointer to the cls_lasvm instance you're dealing with like so:

extern "C"
double
lasvm_kcache_create_callback( int i, int j, void* closure)
{
    // have to get a cls_lasvm pointer somehow, maybe the 
    // void* clpsure is a context value that can hold the
    // this pointer - I don't know

    cls_lasvm* me = reinterpret_cast<cls_lasvm*>( closure);

    return me->kernel( i, j)

}


class cls_lasvm //...
{

    ...

    // the callback that's in the class doens't need kparam
    double cls_lasvm::kernel(int i, int j);

};

...

// called like so, assuming it's being called from a cls_lasvm
//  member function

lasvm_kcache_t *kcache=lasvm_kcache_create(&lasvm_kcache_create_callback, this);

If I'm wrong about closure being a context cookie, then your callback function in the cls_lasvm class needs to be static:

extern "C"
double
lasvm_kcache_create_callback( int i, int j, void* closure)
{
    // if there is no context provided (or needed) then
    // all you need is a static function in cls_lasvm

    return cls_lasvm::kernel( i, j, closure);
}

// the callback that's in the class needs to be static
static double cls_lasvm::kernel(int i, int j, void* closure);

Note that a C callback function implemented in C++ must be extern "C". It may seem to work as a static function in a class because class-static functions often use the same calling convention as a C function. However, doing that is a bug waiting to happen (see comments below), so please don't - go through an extern "C" wrapper instead.

If closure isn't a context cookie and for some reason cls_lasvm::kernel() can't be static then you need to come up with a way to stash a this pointer somewhere and retrieve that pointer in the lasvm_kcache_create_callback() function, similar to the way I did in my first example, except that pointer has to come dfrom some mechanism you devise yourself. Note that this will likely make using lasvm_kcache_create() non-reentrant and non-threadsafe. That may or may not be a problem depending on your specific circumstances.

Michael Burr
Unfortunately I have been bitten by the static method not having the same calling convention as the 'C' function. And given the way that C+= compilers are moving I doubt that will stay true. Highly optimized compilers are going to take advantage of every possible technique to get speed and thus passing parameters in register instead of the stack will start to become more and more common (it is only a mater of time).
Martin York
@Martin: OK - I'll remove the suggestion that it's likely to be safe. Man - there is a *lot* of code that will break if/when those optimizations (or whatever) become commonplace. I'd guess that more than 75% of the time I see C callbacks being used in C++ code it's done directly through a static member function.
Michael Burr
@Michael: I see it a every now and then with younger coders. But in big companyies these mistakes always get cough at code review (and once cough the mistake is not made again as we can make it quite embarrassing). So I have seen it (not a lot) in open source projects were wise heads heads are not bashing the younger generation.
Martin York
@Martin - I'm guessing you don't deal with much Windows code (just as I don't see very much Unix code) - I see it all the time there. I'll be very surprised if MSVC ever breaks using static member functions as C callbacks.
Michael Burr