null pointer when getting function pointer using boost::function::target
Asked Answered
K

3

1

After reading this answer I thought I had a solution. At least the answer there is what I would like to do but I'm having a problem with the implementation.

here is an outline of what I am trying to do

typedef map<string, double*> myMap;
typedef int (*ftwpt)(const char*, const struct stat*, int);
typedef boost::function<int(const char*, const struct stat*, int)> MyFTWFunction;

int myFunction(const char*, const struct stat*, int, myMap*);

int main()
{
myMap m_map;
char tmpdir[] = "/tmp/mytmp";

MyFTWFunction f = boost::bind(myFunction,_1,_2,_3, &m_map);

ftwpt* fpt = f.target<ftwpt>();
if (fpt)
    status = ftw(tmpdir, *fpt, 50);
else
{
    cout << "Boost could not perform runtime conversion on function pointer" << endl;
    return (EXIT_FAILURE);
}
}

the program compiles with no errors or warnings but I am getting a null pointer (fpt) returned from f.target(); at runtime. From references linked on the above stackoverflow question it seems a null pointer is returned if boost is unable to perform the runtime conversion. But I have no idea why Boost might not be able to perform the runtime conversion. Any ideas?

Kamp answered 4/9, 2009 at 19:13 Comment(2)
@Konrad on the other question also explains why your code fails: #282872Beheld
@Kamp - This question is about C++, not C. Please try to tag it properly.Vintage
B
2

For that to work, you would need to know the exact type of the bind expression that you store into the boost::function object. The object boost::bind(....) returns is some weird expression template, not a function pointer.

To understand why this is needed, consider how boost::function is implemented in principle

struct base { virtual ~base() { } };

template<typename T>
struct derived : base {
  derived(T t):t(t) { }
  T t;
};

struct function {
  template<typename T>
  function(T t) {
    base *b = new derived<T>(t);
  }

  template<typename T>
  T *target() {
    if(typeid(*b) == typeid(derived<T>))
      return &static_cast< derived<T>* >(b)->t;
    return 0;
  }

  base *b;
};

That's the most fundamental structure, without the operator() bloat - much like boost::any. The mechanism is called type-erasure: The constructor accepts objects of arbitrary types, and then stores an object encapsulated into an object that you may reach through virtual function calls (boost::function is optimized like hell, using its own vtable and stack-allocation to avoid new for small types and so on).

For function pointers, this works great, because you know the type of the function that you assign to the boost::function object. But for complex callable objects, it doesn't quite work anymore.

To be able to see it working and to see that it's not just working with function pointers, but also with bind expressions, consider the following code

template<typename T>
struct id { typedef T type; };

template<typename T>
id<T> make_id(T) { return id<T>(); }

struct any_type {
  template<typename T>
  operator id<T>() const { return id<T>(); }
};

template<typename T, typename Fn>
T *get_target(boost::function<Fn> &f, id<T>)
{ return f.template target<T>(); }

void f(int a, int b) { std::cout << a << " " << b << std::endl; }

int main() {
  boost::function<void(int)> g = boost::bind(&f, _1, 10);
  (*get_target(g, true ? any_type() : make_id(boost::bind(&f, _1, 10))))(2);
}

Within get_target you know the type of what boost::bind returns. You can use that to call the target call and return the object that's wrapped inside the boost::function. Within main we then call the bind expression. Please read Eric Niebler's article Conditional Love to see how this code snippet works.

Beheld answered 4/9, 2009 at 19:33 Comment(5)
So what is an appropriate way to go about this? I want to use a function as a callback function for ftw, but I also want to pass a pointer to a data structure that the callback and put results in. It seems like Boost.Bind should be able to solve this kind of problem, so how do I go about it?Kamp
Why not make ftw a template? Then it can accept anything from a function pointer to boost::function over a boost::bind. If you don't want to make it a template, why not use boos::function as its parameter type? That's what it's for :)Beheld
I should have given more details. ftw is a function in a shared library (see man ftw at linux.die.net/man/3/ftw), I don't have any control over its parameter list.Kamp
you are out of luck then. create a wrapper function that forwards to the real function and passes the other parameter in addition. boost::bind cannot be used then. :(Beheld
One of the rules of library design is that for callback functions you always add a parameter that the library user can use to get your library to hand back a bit of user supplied data. I just thought of a possible solution, but it's too ugly for words.Abscise
A
1

The other answer points out why your code doesn't work. Here is a really ugly solution that sort of does, kind of, for certain limited situations.

typedef int (*ftwpt)(const char*, const struct stat*, int);
typedef boost::function<int(const char*, const struct stat*, int)> MyFTWFunction;

template <MyFTWFunction *callback>
class callback_binder {
 public:
   static int callbackThunk(const char *s, const struct stat *st, int i) {
      return (*callback)(s, i);
   }
};

extern void register_callback(callback_t f);

int random_func(const char *s, const struct stat *st, int i)
{
   if (s && *s) {
      return i;
   } else {
      return -1;
   }
}

MyFTWFunction myfunc;

int main(int argc, const char *argv[])
{
   myfunc = random_func;
   register_callback(&callback_binder<&myfunc>::callbackThunk);
   return 0;
}

The rules for using pointers as template arguments require that the pointer passed in as an argument be a pointer to a global variable. That global variable can, of course, be declared in an anonymous namespace.

It's ugly, and if you wanted to have several possible instances of myMap possible called back with at the same time you'd need as many global MyFTWFunction variables as possible simultaneous instances of myMap. Mostly this automates the creation of a thunk function that uses the content of a global variable to fill in the missing parameter.

Here is a version that is a LOT less flexible that does approximately the same thing for this narrow case that may make it more obvious what's going on here:

#include <map>
#include <string>

using ::std::map;
using ::std::string;
typedef map<string, double*> myMap;
typedef int (*callback_t)(const char *, struct stat *st, int);

int myFunction(const char*, struct stat *st, int, myMap*);

template <myMap **map_ptr>
class myMap_binder {
 public:
   static int call_my_function(const char *s, struct stat *st, int i) {
      return myFunction(s, st, i, *map_ptr);
   }
};

extern void register_callback(callback_t f);

myMap *mainmap;
myMap *othermap;

int main(int argc, const char *argv[])
{
   myMap m_map;
   myMap m_map2;
   mainmap = &m_map;
   othermap = &m_map2;
   register_callback(&myMap_binder<&mainmap>::call_my_function);
   register_callback(&myMap_binder<&othermap>::call_my_function);
   return 0;
}

As you can see myMap_binder is a template that auto-generates thunk functions that stuff in the contents of a global variable into a call to your callback function.

Abscise answered 5/9, 2009 at 1:54 Comment(0)
D
0

This is a few years late, but maybe it'll help you in the future. My problem was slightly different, but you can still get the answer you want from the solution; read here:
> Messaging system: Callbacks can be anything

Dittman answered 31/7, 2011 at 16:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.