14

Premise

I’m using a C library (from C++) which provides the following interface:

void register_callback(void* f, void* data);
void invoke_callback();

Problem

Now, I need to register a function template as a callback and this is causing me problems. Consider the following code:

template <typename T> void my_callback(void* data) { … }

int main() {
    int ft = 42;
    register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);
    invoke_callback();
}

This gives me the following linker error (using g++ (GCC) 4.5.1 on OS X but works on most other combinations of compiler version / platform):

Undefined symbols for architecture x86_64:

"void my_callback<int>(void*)", referenced from:  
  _main in ccYLXc5w.o

which I find understandable.

First “solution”

This is easily fixed by explicitly instantiating the template:

template void my_callback<int>(void* data);

Unfortunately, this isn’t applicable in my real code since the callback is registered inside a function template, and I don’t know for which set of template arguments this function will be called, so I can’t provide explicit instantiations for all of them (I’m programming a library). So my real code looks a bit like this:

template <typename T>
void do_register_callback(T& value) {
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
    // Other things …
}

int main() {
    int ft = 42;
    do_register_callback(ft);
    invoke_callback();
}

Second “solution”

A function template is implicitly instantiated by calling the function. So let’s do that, but make sure that the call isn’t actually performed (the function has got side-effects):

template <typename T>
void do_register_callback(T& value) {
    if (false) { my_callback<T>(0); }
    register_callback(reinterpret_cast<void*>(my_callback<T>), &value);
}

This seems to work, even with optimisations enabled (so that the dead branch is removed by the compiler). But I’m not sure if this won’t some day break down. I also find this a very ugly solution that requires a length explanatory comment lest some future maintainer remove this obviously unnecessary code.

Question

How do I instantiate a template for which I don’t know the template arguments? This question is obviously nonsense: I can’t. – But is there a sneaky way around this?

Barring that, is my workaround guaranteed to succeed?

Bonus question

The code (specifically, the fact that I cast a function pointer to void*) also produces the following warning:

ISO C++ forbids casting between pointer-to-function and pointer-to-object

when compiling with -pedantic. Can I somehow get rid of the warning, without writing a strongly-typed C wrapper for the library (which is impossible in my situation)?

Running code on ideone (with an added cast to make it compile)

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • btw - the `if(false)` is redundant - as the `static_cast` forces instantiation anyway. – Nim Jul 18 '11 at 14:35
  • @Nim It’s *not* redundant on GCC 4.5, otherwise I wouldn’t have the problem in the first place. – Konrad Rudolph Jul 18 '11 at 14:38
  • really? the following works just fine - that's 4.3.4: http://ideone.com/B6TTY – Nim Jul 18 '11 at 14:39
  • 3
    Why isn't the callback signature `void (*)(void*)` in register callback? – David Rodríguez - dribeas Jul 18 '11 at 14:46
  • 1
    @David Don’t ask me, I haven’t the slightest inkling. This is from a third-party C library. – Konrad Rudolph Jul 18 '11 at 14:47
  • 1
    Why not use `if(false) { my_callback(value); }` instead of assuming that all callbacks will use pointer values? It's at least slightly less ugly. – Chris Lutz Jul 18 '11 at 14:49
  • 1
    @Chris: given that the callback is invoked with a `void*`, I guess it will always use a pointer argument. – Matthieu M. Jul 18 '11 at 14:51
  • 1
    Also, using `(void *)` instead of `reinterpret_cast` may get rid of the warning. If it doesn't you could always use a union. – Chris Lutz Jul 18 '11 at 14:53
  • @Matthieu M. - I originally thought it was a numeric argument because I do mostly C where we use `NULL` instead of a raw 0. It's possible to pass a number as a pointer, reinterpreting the pointer value as an integer, but since the OP's code is shown passing a poInter to an `int` it does seem unlikely. Oh well, everyone misreads sometimes. – Chris Lutz Jul 18 '11 at 14:56
  • @Chris It doesn’t, and I don’t see why it should – a C-style cast can only be either a `static_cast` or a `reinterpret_cast` (or something else which I forgot but which doesn’t apply here). The cast is still being performed. – Konrad Rudolph Jul 18 '11 at 15:10
  • @Konrad - I'll admit I'm lacking in my understanding of C++ casts (I stick to C out of preference) but if the compiler warns (correctly, as you know) about the actual _cast_ of a function pointer to an object pointer, a union appears to be the only way to silence it (aside from adjusting your compiler settings). – Chris Lutz Jul 18 '11 at 16:22
  • @Chris I completely missed the suggestion of using a union. Good call, that works. – Konrad Rudolph Jul 18 '11 at 18:33

3 Answers3

8

Apparently, the real problem was the missing static_cast in my original code:

register_callback(reinterpret_cast<void*>(&my_callback<int>), &ft);

This compiles fine, but triggers the liker error when using GCC 4.5. It doesn’t even compile when using GCC 4.2, instead giving the following compile error:

insufficient contextual information to determine type

Once this “contextual information” is provided, the code compiles and links:

register_callback(reinterpret_cast<void*>(
    static_cast<void(*)(void*)>(my_callback<int>)), &value);

I’ve got no idea whether the cast is actually required and (if so) why GCC 4.5 allows me to leave it off, and then fails to instantiate the template. But at least I got the code to compile without resorting to hacks.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • Does that mean that you have more than one overload for the templated function? If there is a single template, there should be no need for the extra "contextual information" to determine the type (i.e. the type would be uniquely defined by the type parameter to the template) – David Rodríguez - dribeas Jul 18 '11 at 15:08
  • @David I haven’t. The code is exactly as-is in the ideone.com code. – Konrad Rudolph Jul 18 '11 at 15:11
  • @Konrad: now that you mention the fix, I realize I have already have to use a `static_cast` in a similar situation. I didn't dwelled on it... but never really understood WHY it was necessary. – Matthieu M. Jul 18 '11 at 15:34
  • ..so the last line actually works on your platform/compiler combination? and what I posted doesn't? interesting... – Nim Jul 18 '11 at 15:34
  • @Nim Let me re-evaluate this. I think you’re right, this is effectively equivalent to the `static_cast`. I don’t have time right now but I’ll do this first thing tomorrow. – Konrad Rudolph Jul 18 '11 at 15:38
  • I'm curious if you ever figured this out, as I've ran into the same issue. The behavior is the same in clang, so I assume it's not a bug. However, it seems like it the compiler should be able to figure it out. Additionally, it seems like the compiler succeeds in figuring this out as it triggers the linker error. So it seems like the question is, why does an implicit cast to function pointer *not* instantiate a function template, while an explicit static_cast does? – Robert Mason Oct 22 '12 at 01:45
  • @Robert Well the second code works. But Luc’s solution (accepted answer) is better because it doesn’t rely on undefined behaviour. – Konrad Rudolph Oct 22 '12 at 07:36
  • My specific issue doesn't have to do with casting function pointers to data pointers - rather, it deals with the template function not being instantiated. So I have the same thing, minus the reinterpret_cast (the register function takes a function pointer), and without a static cast there, the template does not instantiate. The compiler accepts the code without a static cast without errors until link-time when it can't find the function in .text – Robert Mason Oct 22 '12 at 16:42
  • @Robert Hmm. Like I said in my answer, it actually does work with the `static_cast`. If this continues to be a problem for you try the Stack Overflow [C++ chat](http://chat.stackoverflow.com/rooms/info/10/loungec). People there are generally grumpy about questions but they make exceptions for interesting ones (such as this one), and they are quite resourceful. – Konrad Rudolph Oct 22 '12 at 16:49
5

This should work:

template <typename T>
void do_register_callback(T& value) {
   void (*callback)(void*) = my_callback<T>;
   register_callback(reinterpret_cast<void*>(callback), &value);
}

The first line forces the compiler to instantiate that function to generate the address - which you can then happily pass.

EDIT: Let me throw another option in to this mix. Make my_callback a static member of a class template - something like the following:

template <typename T>
struct foo
{
static void my_callback(void* data) {
    T& x = *static_cast<T*>(data);
    std:: cout << "Call[T] with " << x << std::endl;
}
};

Now, in your register guy, you don't even need the "cast".

template <typename T>
void do_register_callback(T& value) {
   register_callback(reinterpret_cast<void*>(&foo<int>::my_callback), &value);
}

It appears that the rule for instantiating class templates is different to function templates - i.e. to take the address of a member of a class, the type is instantiated.

Nim
  • 33,299
  • 2
  • 62
  • 101
  • 1
    I think you mean my_callback – zennehoy Jul 18 '11 at 14:47
  • This sounds reasonable. Unfortunately, it definitely doesn’t work on my compiler/platform combination. For reference, your code also works on my platform with a different compiler version (4.2), and it works with my compiler version (4.5.1) on a different platform. – Konrad Rudolph Jul 18 '11 at 14:50
  • @zennehoy, @Nim: seems obvious, I patched it. – Matthieu M. Jul 18 '11 at 14:52
4

POSIX recommends the following way to cast between function pointer types and object pointer types (which is undefined in C99):

typedef void function_type(void*);
function_type *p_to_function = &my_callback<T>;
void* p_to_data = *(void**)&p_to_function;

// Undefined:
// void* p_to_data = (void*)p_to_function;

Notice that in C++-land, this would perform a reinterpret_cast<void**>(&p_to_function) from function_type**. This is not undefined but instead implementation-defined, unlike reinterpret_cast<void*>(p_to_function). So it's probably your best bet to write C++-conformant code that relies on the implementation.

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • So the pointer-to-pointer-to-function cast is to be de-referenced and passed to the callback registration? i.e. `*reinterpret_cast(&p_to_function)` - and this is implementation defined? – Nim Jul 18 '11 at 16:17
  • ..the above generates warnings about type punned pointer de-referencing – Nim Jul 18 '11 at 16:19
  • @Nim Yes, `reinterpret_cast`'s between unrelated pointer types is implementation-defined. – Luc Danton Jul 18 '11 at 16:20
  • must be late in the day, I don't get the difference between that extra step through the additional indirection and simply `reinterpret_cast`ing the function pointer to `void*` (it is effectively a cast between two different pointer types - is it not?) and therefore should be implementation defined? – Nim Jul 18 '11 at 16:24
  • @Nim When I use 'pointer type' it excludes 'pointer to function type' from its meaning (parse the latter as (pointer to function) type by the way). The Standard terminology is actually 'object pointer type', 'function pointer type' and 'member pointer type'. I'm going to amend my answer. – Luc Danton Jul 18 '11 at 16:25
  • @Nim I also found (and fixed) another mistake in my answer. – Luc Danton Jul 18 '11 at 16:45