7

This question is for tests purposes, nothing more.

I'm currently trying to store function pointers with a different number of parameters (and these parameters can have different types).

Basically, I've coded the following code snippet in C++11:

#include <functional>
#include <iostream>

void fct(int nb, char c, int nb2, int nb3) {
  std::cout << nb << c << nb2 << nb3 << std::endl;
}

template <typename... Args>
void call(void (*f)(), Args... args) {
  (reinterpret_cast<void(*)(Args...)>(f))(args...);
}

int main(void) {
  call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, 94);
}

I convert a void(*)(int, char, int, int) function pointer into a generic void(*)() function pointer. Then, by using variadic template parameters, I simply recast the function pointer to its original type and call the function with some parameters.

This code compiles and runs. Most of the times, it displays the good values. However, this code gives me some Valgrind errors under Mac OS (concerning uninitialized values) and it sometimes displays some unexpected garbage.

==52187== Conditional jump or move depends on uninitialised value(s)
==52187==    at 0x1004E4C3F: _platform_memchr$VARIANT$Haswell (in /usr/lib/system/libsystem_platform.dylib)
==52187==    by 0x1002D8B96: __sfvwrite (in /usr/lib/system/libsystem_c.dylib)
==52187==    by 0x1002D90AA: fwrite (in /usr/lib/system/libsystem_c.dylib)
==52187==    by 0x100025D29: std::__1::__stdoutbuf<char>::overflow(int) (in /usr/lib/libc++.1.dylib)
==52187==    by 0x10001B91C: std::__1::basic_streambuf<char, std::__1::char_traits<char> >::xsputn(char const*, long) (in /usr/lib/libc++.1.dylib)
==52187==    by 0x10003BDB0: std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) (in /usr/lib/libc++.1.dylib)
==52187==    by 0x10003B9A7: std::__1::num_put<char, std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > >::do_put(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, std::__1::ios_base&, char, long) const (in /usr/lib/libc++.1.dylib)
==52187==    by 0x1000217A4: std::__1::basic_ostream<char, std::__1::char_traits<char> >::operator<<(int) (in /usr/lib/libc++.1.dylib)
==52187==    by 0x1000011E8: fct(int, char, int, int) (in ./a.out)
==52187==    by 0x1000013C2: void call<int, char, int, int>(void (*)(), int, char, int, int) (in ./a.out)
==52187==    by 0x100001257: main (in ./a.out)

I find this quite curious because when I call the function, I have recasted the function pointer to its original type. I thought it was similar to casting a datatype to void* and then recasting it into the original datatype.

What is wrong with my code? Can't we cast function pointers to void(*)() pointer and then recast this pointer to the original function pointer signature?

If not, is there some other ways to achieve this? I'm not interested in std::bind which does not what I want.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
Simon Ninon
  • 2,371
  • 26
  • 43
  • You mean function pointers? Not pointers to member functions? – MSalters Jun 30 '15 at 22:12
  • 1
    I can't reproduce that. Is that your actual, literal test case? – Kerrek SB Jun 30 '15 at 22:12
  • Yes, this is exactly the code I've run (on macos Yosemite). Moreover, if I replace the last parameter by an std::string, I got garbage. – Simon Ninon Jun 30 '15 at 22:14
  • @MSalters yes, I'm focusing on function pointers for the moment (and not pointers to member functions) – Simon Ninon Jun 30 '15 at 22:16
  • 2
    [expr.reinterpret.cast] says that you can reinterpret cast a function pointer there and back again and get the original value back. Maybe you have a compiler bug? – Kerrek SB Jun 30 '15 at 22:17
  • Not that it would fix your current problem (which I can't explain), but you'd probably want to make `call` a [perfect forwarder](https://stackoverflow.com/q/3582001/1392132). – 5gon12eder Jun 30 '15 at 22:26
  • 1
    You should post your failing code. If you are passing `"foobar"` as the argument to `std::string`, then that explains the problem. `call()` doesn't know it has to cast `"foobar"` to `std::string` because there is no function prototype. – Mark Lakata Jul 01 '15 at 02:07
  • Does this exact program give different results when ran many times? – n. m. could be an AI Jul 01 '15 at 03:13

2 Answers2

2

You said you're also interested in alternative implementations. Personally, I wouldn't implement things this way even if it worked perfectly, both function pointers and reinterpret_casts are things I try to avoid. I haven't tested this code, but my thought would be:

#include <functional>
#include <iostream>
#include <boost/any.hpp>

template <typename... Args>
void call(boost::any clbl, Args... args) {
  auto f = boost::any_cast<std::function<void(Args...)>>(clbl);
  f(args...);
}

int main(void) {
  std::function<void(int, char, int, int)> func = fct;
  call(boost::any(func), 42, 'c', 19, 94);
}

Edit: this code, combined with your definition of fct, works correctly, and runs clean under valgrind on Fedora, compiled with clang35.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • 1
    Code is clearer, but I think this only hides the reinterpret_cast (which is surely done internally in the boost::any_cast implementation). However, thank you for your example, I haven't heard about boost::any and boost::any_cast until now! – Simon Ninon Jul 01 '15 at 14:01
  • 1
    It's actually a static_cast internally. There are even variants of boost::any that use a dynamic cast internally... pretty nice to get an exception instead of a segfault if you mess up. – Nir Friedman Jul 01 '15 at 14:24
2

Going out on a limb and guessing what you did to get it to fail...

#include <functional>
#include <iostream>

void fct(int nb, char c, int nb2, std::string nb3) {
  std::cout << nb << c << nb2 << nb3 << std::endl;
}

template <typename... Args>
void call(void (*f)(), Args... args) {
  (reinterpret_cast<void(*)(Args...)>(f))(args...);
}

int main(void) {
  call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, "foobar");
}

This will fail because "foobar" never gets converted to std::string ... how can the compiler know if it goes through Args... ?

I'm not sure exactly how std::string gets pushed on the call stack by a caller ( a string reference would be pushed on as a pointer), but I suspect it is more than just a single pointer to char*. When the callee pops off that pointer to char* expecting the entire string member, it freaks out.

I think if you change to

void fct(int nb, char c, int nb2, char* nb3)

or

call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, std::string("foobar"));

then it might work.

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
  • 1
    Logical! Thank you for your explanation. It might be a good idea to look how variadic lists are handled. I think the valgrind error I got is due to the unstable implementation of valgrind on macOSX. – Simon Ninon Jul 01 '15 at 13:58