2

This code here works just fine. It couts
True!
False!
As it should

#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>

//typedef bool(callback) (int, int);
typedef boost::function<bool(int, int)> callback;

void print_result(callback handle, int first, int second)
{
    if(handle == nullptr)
        return; 

    if(handle(first, second))
        std::cout << "True!\n";
    else
        std::cout << "False!\n";
}

class Callback
{
public:
    Callback(bool type) : m_type(type)
    {}
    bool operator() (int foo, int bar)
    {
        return m_type ? foo > bar : foo < bar;
    }
private:
    bool m_type;
};

int main()
{   
    print_result(Callback(false), 2, 3);
    print_result(Callback(true), 2, 3);
    return 0;
}

But unfortunately I must make it work with the good old function pointer. I never use them in my practice, and I don't know much about them. It's obvious here that signature "bool operator() (int foo, int bar)" is not easily convertible to "bool(callback) (int, int)".

The error code I get from gcc:

prog.cc: In function 'int main()':
prog.cc:34:18: error: cannot convert 'Callback' to 'bool (*)(int, int)'
     print_result(Callback(false), 2, 3);
                  ^~~~~~~~~~~~~~~
prog.cc:8:28: note:   initializing argument 1 of 'void print_result(bool (*)(int, int), int, int)'
 void print_result(callback handle, int first, int second)
                   ~~~~~~~~~^~~~~~
prog.cc:35:18: error: cannot convert 'Callback' to 'bool (*)(int, int)'
     print_result(Callback(true), 2, 3);
                  ^~~~~~~~~~~~~~
prog.cc:8:28: note:   initializing argument 1 of 'void print_result(bool (*)(int, int), int, int)'
 void print_result(callback handle, int first, int second) 

Is there anyway to solve it? By the way, I wouldn't mind having a different solution. For example, the bool parameter can be passed by using "boost::bind", but binding doesn't work either. For the same reason.
Ideas, anyone? Thanks in advance!

NOTE: I cannot change the signature of a "print_result" function. Solutions like "use X instead of a function pointer" are off the table.

Eugene Mart
  • 185
  • 1
  • 10
  • Function pointers cannot hold extra parameters (as capture of lambda or member of functor)., If you have to use with function pointer, common solution is to provide a `void* userData`. – Jarod42 Nov 09 '18 at 08:49
  • Please tell me more about it. Or provide a useful link. I googled "extra parameters to a function pointer" and similar stuff. – Eugene Mart Nov 09 '18 at 08:55
  • If you absolutely cannot change the signature of `print_result`, then no, you cannot pass it a functor. – Nelfeal Nov 09 '18 at 09:00
  • "I must make it work with the good old function pointer." **Show, don't describe**. What is the signature of the API you must use? Show it. – n. m. could be an AI Nov 09 '18 at 09:02
  • typedef bool(callback) (int, int); void print_result(callback handle, int first, int second); – Eugene Mart Nov 09 '18 at 09:03
  • This might help [https://fscked.org/proj/minihax/autocode/functorptr.cc](https://fscked.org/proj/minihax/autocode/functorptr.cc) – dummy Nov 09 '18 at 09:04
  • Functor can only be converted to function pointer if it is a stateless functor – M.M Nov 09 '18 at 09:13
  • 1
    You need to file a defect report against this interface. Any interface that takes a C-style function pointer callback and doesn't provide for `void* userData` is broken and useless. (Yes, `qsort` from the C library is too, and so are many others). – n. m. could be an AI Nov 09 '18 at 09:32

4 Answers4

2

Traditional way for callback to allow extra parameter is to have extra void* that user provides.

using callback = bool (int, int, void* userData);

bool run_callback(int a, int b, void* userData) {
    CallBack* c = reinterpret_cast<CallBack*>(userData);

    return (*c)(a, b);
}

CallBack t(true);
Register(&run_callback, &t); // Would call later run_callback(2, 3, &t);

If you cannot change the signature, then you might use global to pass that extra argument, so with some restrictions.

Callback* global = nullptr;

bool RunCallBack(int foo, int bar)
{
    assert(global != nullptr);
    return (*global)(foo, bar);
}

And then

Callback f(false);
Callback t(true);

global = &f;
print_result(&RunCallBack, 2, 3);
global = &t;
print_result(&RunCallBack, 2, 3);
Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

If you don't mind passing an additional argument, you can do the following:

#include <iostream>
#include <functional>

template<typename T>
using callback = bool(T::*) (int, int);

template<typename T>
void print_result(T t, callback<T> handle, int first, int second) {
    if(handle == nullptr)
        return;

    if((t.*handle)(first, second))
        std::cout << "True!\n";
    else
        std::cout << "False!\n";
}

class Callback {
public:
    Callback(bool type) : m_type(type) {}
    bool operator() (int foo, int bar) {
        return m_type ? foo > bar : foo < bar;
    }
private:
    bool m_type;
};

int main() {   
    print_result(Callback(false), &Callback::operator(), 2, 3);
    print_result(Callback(true), &Callback::operator(), 2, 3);
}
Nelfeal
  • 12,593
  • 1
  • 20
  • 39
0

Not changing print_result, you have to send different functions based on your arguments to Callback.

It's fairly simple to move a single bool parameter to be a template parameter, but gets unwieldy if there is state that isn't constexpr constructable.

template<bool type>
bool Callback (int foo, int bar);

template<>
bool Callback<true> (int foo, int bar)
{
    return foo > bar;
}

template<>
bool Callback<false> (int foo, int bar)
{
    return foo < bar;
}

int main()
{   
    print_result(Callback<false>, 2, 3);
    print_result(Callback<true>, 2, 3);
    return 0;
}

And if you need to choose based on a runtime value

callback select_callback(bool type)
{
    return type ? Callback<true> : Callback<false>;
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
0

If you cannot change the interface with social engineering and don't want to use globals, there is still a solition. Create a C-ctyle closure/trampoline. Of course this is not doable in standard C or C++, but there are (non-portable) libraries that can do it for you. One such is FFCALL. An example can be found here.

Once you create a closure that closes over your data, you are set. Just make the data a (pointer to) your original boost/std function, and call it.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243