Search code examples
c++castingfunction-pointersreinterpret-cast

How can I get rid of this reinterpret_cast, or is this usage OK?


I have a template member function with this signature:

template<typename T> void sync(void (*work)(T*), T context);

It can be called with a pointer to a function that accepts an argument of type T*. context is passed to that function. The implementation is this:

template<typename T> void queue::sync(void (*work)(T*), T context) {
  dispatch_sync_f(_c_queue, static_cast<void*>(&context),
                  reinterpret_cast<dispatch_function_t>(work));
}

It uses reinterpret_cast<> and it works. The problem is that the standard doesn't define it very well and it is very dangerous. How can I get rid of this? I tried static_cast but that gave me a compiler error:

static_cast from void (*)(std::__1::basic_string<char> *) to dispatch_function_t (aka void (*)(void *)) is not allowed.

dispatch_function_t is a C type and is the same as void (*)(void*).


I'm not sure I was clear enough. What dispatch_sync_f does is it calls a given callback function and passes the given context parameter to that callback function. (It does that on another thread, although that is out of the scope of this question.)


Solution

  • The reason this is not supported by static_cast is because it is potentially unsafe. While a std::string* will convert implicitely to a void*, the two are not the same thing. The correct solution is to provide a simple wrapper class to your function, which takes a void*, and static_casts it back to the desired type, and pass the address of this wrapper function to your function. (In practice, on modern machines, you'll get away with the reinterpret_cast, since all pointers to data have the same size and format. Whether you want to cut corners like this is up to you—but there are cases where it's justified. I'm just not convinced that this is one of them, given the simple work-around.)

    EDIT: One additional point: you say that dispatch_function_t is a C type. If this is the case, the actual type if probably extern "C" void (*)(void*), and you can only initialize it with functions that have "C" linkage. (Again, you're likely to get away with it, but I've used compilers where the calling conventions were different for "C" and "C++".)