Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic Template conversion to std::function<R(ARGS...)> works with GCC and not MSVC2013, why?

Tags:

If this is a duplicate I appologize. I couldn't find anything in my searches though.

I can use any of the newest features c++11/c++14. I can upgrade to VS2015 if necessary.

I'm trying to write a class that will auto cast into a std::function with a specific signature when assigned. I have code that works with GCC, but it failed on MSVC2013. The code is a snippet that recreates the error. WTF MSVC?!

Also I know this is risky code, automatically casting function pointers and such, but it's for the private implementation of a plugin library and I only want to define the function signature once.

If there is another way to write the code that accomplishes the same functionality in main() and works on both, I'm all ears.

GCC c++11 works fine - Demo

#include <functional> #include <string> #include <iostream>  class FunctionPointer {     void* fp; public:     FunctionPointer(void* ptr)         : fp(ptr)     {}      // Overload casting operator to      // a certain function signiture     template<class R, class... ARGS>     operator std::function<R(ARGS...)>(){         typedef R(*func_ptr)(ARGS...);         return std::function<R(ARGS...)>((func_ptr)fp);     } };  void hello(std::string msg){     std::cout << "Hello " << msg << std::endl; }  int main() {      FunctionPointer f((void*)hello);      std::function<void(std::string)> func_hello = f;      func_hello("World!");      return 0; } 

MSVC works when I change the line to this...

std::function<void(std::string)> func_hello = f.operator std::function<void(std::string)>(); 

MSVC fails with the same error when I have this...

std::function<void(std::string)> func_hello = (std::function<void(std::string)>)f; 

MSVC fails with the following error in a file that is hard to read to say the least. It seems to be deducing the wrong function signature.

xrefwrap.h:283 - error C2064: term does not evaluate to a function taking 1 arguments   1>c:\program files (x86)\microsoft visual studio 12.0\vc\include\xrefwrap(283): error C2064: term does not evaluate to a function taking 1 arguments 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(228) : see reference to function template instantiation '_Ret std::_Callable_obj<FunctionPointer,false>::_ApplyX<_Rx,_Ty>(_Ty &&)' being compiled 1>          with 1>          [ 1>              _Ret=void 1>  ,            _Rx=void 1>  ,            _Ty=std::basic_string<char,std::char_traits<char>,std::allocator<char>> 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(228) : see reference to function template instantiation '_Ret std::_Callable_obj<FunctionPointer,false>::_ApplyX<_Rx,_Ty>(_Ty &&)' being compiled 1>          with 1>          [ 1>              _Ret=void 1>  ,            _Rx=void 1>  ,            _Ty=std::basic_string<char,std::char_traits<char>,std::allocator<char>> 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(226) : while compiling class template member function 'void std::_Func_impl<_MyWrapper,_Alloc,_Ret,std::string>::_Do_call(std::string &&)' 1>          with 1>          [ 1>              _Alloc=std::allocator<std::_Func_class<void,std::string>> 1>  ,            _Ret=void 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(495) : see reference to class template instantiation 'std::_Func_impl<_MyWrapper,_Alloc,_Ret,std::string>' being compiled 1>          with 1>          [ 1>              _Alloc=std::allocator<std::_Func_class<void,std::string>> 1>  ,            _Ret=void 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(396) : see reference to function template instantiation 'void std::_Func_class<_Ret,std::string>::_Do_alloc<_Myimpl,FunctionPointer&,_Alloc>(_Fty,_Alloc)' being compiled 1>          with 1>          [ 1>              _Ret=void 1>  ,            _Alloc=std::allocator<std::_Func_class<void,std::string>> 1>  ,            _Fty=FunctionPointer & 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(396) : see reference to function template instantiation 'void std::_Func_class<_Ret,std::string>::_Do_alloc<_Myimpl,FunctionPointer&,_Alloc>(_Fty,_Alloc)' being compiled 1>          with 1>          [ 1>              _Ret=void 1>  ,            _Alloc=std::allocator<std::_Func_class<void,std::string>> 1>  ,            _Fty=FunctionPointer & 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(385) : see reference to function template instantiation 'void std::_Func_class<_Ret,std::string>::_Reset_alloc<FunctionPointer&,std::allocator<std::_Func_class<_Ret,std::string>>>(_Fty,_Alloc)' being compiled 1>          with 1>          [ 1>              _Ret=void 1>  ,            _Fty=FunctionPointer & 1>  ,            _Alloc=std::allocator<std::_Func_class<void,std::string>> 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(385) : see reference to function template instantiation 'void std::_Func_class<_Ret,std::string>::_Reset_alloc<FunctionPointer&,std::allocator<std::_Func_class<_Ret,std::string>>>(_Fty,_Alloc)' being compiled 1>          with 1>          [ 1>              _Ret=void 1>  ,            _Fty=FunctionPointer & 1>  ,            _Alloc=std::allocator<std::_Func_class<void,std::string>> 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(671) : see reference to function template instantiation 'void std::_Func_class<_Ret,std::string>::_Reset<FunctionPointer&>(_Fty)' being compiled 1>          with 1>          [ 1>              _Ret=void 1>  ,            _Fty=FunctionPointer & 1>          ] 1>          c:\program files (x86)\microsoft visual studio 12.0\vc\include\functional(671) : see reference to function template instantiation 'void std::_Func_class<_Ret,std::string>::_Reset<FunctionPointer&>(_Fty)' being compiled 1>          with 1>          [ 1>              _Ret=void 1>  ,            _Fty=FunctionPointer & 1>          ] 1>          c:\users\cameron\desktop\desktop\programming\projects\c++ projects\garbage\templatetest\main.cpp(32) : see reference to function template instantiation 'std::function<void (std::string)>::function<FunctionPointer&>(_Fx)' being compiled 1>          with 1>          [ 1>              _Fx=FunctionPointer & 1>          ] 1>          c:\users\cameron\desktop\desktop\programming\projects\c++ projects\garbage\templatetest\main.cpp(32) : see reference to function template instantiation 'std::function<void (std::string)>::function<FunctionPointer&>(_Fx)' being compiled 1>          with 1>          [ 1>              _Fx=FunctionPointer & 1>          ] 
like image 371
extracrispy Avatar asked Jun 28 '15 21:06

extracrispy


2 Answers

This is a different approach to solve your problem. If my understanding of MSVC 2015's capabilities is right, it should work there.

(I assume your problem is wanting to relatively transparently cast a void* to an unknown function into a std::function with the signature that unknown function actually has, without having to repeat the signature of the function.)

Instead of casting the void pointer at the point where we are cast to the std::function, instead I do it at the point where the function is called and the return value calculated (or, when no return value is calculated) via a "return type deduction" trick:

template<class...Args> struct void_ptr_deferred_execution_t {   std::tuple<Args&&...> args;   void const* pf = nullptr;   void_ptr_deferred_execution_t( std::tuple<Args&&...> a, void const* p ):     args(std::move(a)),     pf(p)   {}   template<class R, size_t...Is>   R invoke( std::index_sequence<Is...> ){     using f_t = R(*)(Args...);     f_t f = f_t(pf);     pf = nullptr;     return f(std::forward<Args>(std::get<Is>(args))...);   }   template<class R>   operator R()&&{     return invoke<R>( std::index_sequence_for<Args...>{} );   }   ~void_ptr_deferred_execution_t() {     if (pf) invoke<void>(std::index_sequence_for<Args...>{});   } };  class FunctionPointer {   void* fp; public:   FunctionPointer(void* ptr)     : fp(ptr)   {}    template<class...Args>   void_ptr_deferred_execution_t<Args...>   operator()(Args&&...args)const {     return { std::forward_as_tuple(std::forward<Args>(args)...), fp };   } }; 

live example.

When the std::function invokes their callable, they either discard the result or cast it to an R. From the arguments passed to the callable, plus the type the return value is cast to, I can reconstruct (mostly) the signature of the std::function that called me.

At that point, I cast my void* into a pointer to that kind of function, call it, and return the result.

If I am never cast to something, at the point of destruction I cast my function pointer to a void returning function, call it, and am done.


Cautions:

Note that directly calling FunctionPointer is as dangerous as passing it to a std::function in your example (extremely).

Really, storing a function pointer in a std::function is overkill.

Not tested in MSVC2015, but I don't see anything that wouldn't work in MSVC2015.

There is a possibility that some implementations of std::function may fail to work with the above if your function returns void. It would, however, be a compile-time error.

The above also assumes that there are no functions with rvalue reference arguments, in that at the point of calling, I cannot distinguish between being called from a std::function<void(T)> and a std::function<void(T&&)>. I presume it is a void(T) in that case.

like image 98
Yakk - Adam Nevraumont Avatar answered Oct 26 '22 10:10

Yakk - Adam Nevraumont


This is how I would avoid some casting outside the class:

class FunctionPointer {  void* fp;     public: FunctionPointer() = default;  template<class Ret, class Fn, class... Args> void assign_fn(Fn fn, Args... args) {     std::function<Ret(Args...)> *f = new std::function<Ret(Args...)>;     *f = Fn; // Tip: you can use enable_if combined with is_assignable to make sure this can be done.     // Both are regular pointers, so conversion shouldn't be a problem.     fp = reinterpret_cast<void*>(f); }  // Overload casting operator to  // a certain function signature template<class R, class... ARGS> operator std::function<R(ARGS...)>(){     typedef R(*func_ptr)(ARGS...);     return std::function<R(ARGS...)>((func_ptr)fp); } }; 

Additionally, you might take advantage of the fact that std::function can point to any callable object, by making your function pointer callable (add an operator()), which would be very similar to converting to std::function.

template<class Ret, class... Args> Ret operator()(Args... args) {      std::function<Ret(Args...)> f = *(reinterpret_cast<std::function<Ret(Args...)>*>(fp));      return f(args...); } 
like image 31
Dani Avatar answered Oct 26 '22 11:10

Dani