1

I've found this interesting code here on stackoverflow from: Using a STL map of function pointers

template<typename T,typename... Args>
    T searchAndCall(std::string s1, Args&&... args){

// ....
        // auto typeCastedFun = reinterpret_cast<T(*)(Args ...)>(mapVal.first);
        auto typeCastedFun = (T(*)(Args ...))(mapVal.first);

        //compare the types is equal or not
        assert(mapVal.second == std::type_index(typeid(typeCastedFun)));
        return typeCastedFun(std::forward<Args>(args)...);
    }
};

Basically, mapVal is a map of function pointers casted to void(*)(void) that will be casted back to their original type with this function. What I would like to do know is how typeCastedFun will be deduced when you don't specify the template parameters.

For instance, let's suppose that you had:

int f(const MyClass& a, MyClass b) {...}

... if you have:

MyClass first, second;
searchAndCall<int>(first, second);

What Args... parameter will be deduced? if I recall correctly, using the function casted back to a function with a different signature compared to the original one, should yield undefined behavior. Is there any other alternative?

What I would like to do is a way to store the type of the function somewhere and use this information to do the correct cast. Everything in the most efficient way.

Thanks

[edit1] More specifically, I'm trying to build a kind of generic function dispatcher, able to call functions (templated with an enum class value) with different signatures using a lookup table for efficiency reasons. No boost::any as it internally uses a new

[edit2] Use of macros is not allowed

Community
  • 1
  • 1
user3770392
  • 453
  • 5
  • 12
  • `Args...` would be `MyClass&, MyClass&`. – Jarod42 Mar 29 '17 at 08:12
  • that means undefined behavior right? I definitely need an alternative solution then.. – user3770392 Mar 29 '17 at 08:12
  • BTW, `std::string` is missing in call site. – Jarod42 Mar 29 '17 at 08:13
  • The best solution of all is to refactor your code so that it doesn't involve storing objects of different types in the same container. – Brian Bi Mar 29 '17 at 08:13
  • Yes, you so have UB. (I wonder if even with the right type, UB happens). – Jarod42 Mar 29 '17 at 08:15
  • yes, that would be the ideal solution.. I'm trying to build a kind of generic function dispatcher, able to call functions (templated with an enum class value) with different signatures using a lookup table for efficiency reasons. Another idea I have is to store Structs that inherite from a same base class, but I fear that would be not so efficient – user3770392 Mar 29 '17 at 08:16
  • @Jarod42 with the same type it should not be UB, as far as I know – user3770392 Mar 29 '17 at 08:17
  • Statically typed languages has traditionally been bad at collections of heterogeneous data. C++ have [Boost any](http://www.boost.org/doc/libs/1_63_0/doc/html/any.html) (and now [`std::any`](http://en.cppreference.com/w/cpp/utility/any)) to help alleviate the problem, but it's still lacking behind other more dynamically typed languages. Whatever solutions that exists will always be kind of "hackish", and probably borderline UB. – Some programmer dude Mar 29 '17 at 08:23
  • yes, I'm aware of boost any. It uses a pointer allocated with new and I would like to avoid that. I can't use std::any as I'd like to stay with C++11 atm. I totally agree with you.. what I want to do is a very simple thing that just would allow to save a lot code duplication.. – user3770392 Mar 29 '17 at 08:28
  • 1
    So, you have a bunch of functions. And an enum? And you template the function with the enum? And you have a lookup table "for efficiency"? And the arguments you call them with are decoupled from the table? And the arguments of the function you are calling are not standardized? And you are **worried about memory allocation?!** when you have this mess of a design? Are there any limitations on the types of arguments *at all*? Second, are you by any chance writing some scripting engine? – Yakk - Adam Nevraumont Mar 29 '17 at 13:19
  • @Yakk yes, I'm worried about memory allocation. I don't think it's so stupid what I would like to achieve. It's an function dispatcher in C++11 similar to the one of this link [>=c++14]: https://kfrlib.com/blog/how-c14-and-c17-help-to-write-faster-and-better-code-real-world-examples/ that will get rid of switches in many cases. A thing that I think will semplify the design of many many things.. I've already succed in make it work with functions with similar signature but I'm trying to do the same for functions with different signature – user3770392 Mar 29 '17 at 13:26
  • @Yakk I've created something similar to that, that it seems working for the inputs I've used. Problem is that it is that the cast I'm using is Undefined Behavior, so I'm trying new options.. – user3770392 Mar 29 '17 at 13:32
  • I understand the utility of a runtime to compile time switch, I've written dozens. You are composing this with something that is rarely useful outside of writing a scripting engine (or extremely rich observer engine, like C# widgets), which is storing different signatures of functions together in a single lookup table. It is that second thing that I find insane; I don't see the utility in erasing the signature here. And without a use case, I cannot determine if I'm right and you are barking up the wrong tree. – Yakk - Adam Nevraumont Mar 29 '17 at 14:13
  • @Yakk Suppose you have f<1>(int), f<2>(int,int), f<3>(int,int,int>, a value that you have at runtime (n) that allows you to select which function to use and an array of M inputs int InputManager[M] that are updated at runtime. If n=2 for instance, the compile will generate f<2>(M[0],M[1]), if n=1 f<1>(M[0]) etc.. this is just a stupid example. What I'm trying to do it's just to make the runtime to compile time switch more general. Probably erasing the signature is not the correct approach, it's just one of the tens I've tried so far.. – user3770392 Mar 29 '17 at 14:32
  • in my example, of course you can pass the array to all the functions, but suppose you had to take the data from different input sources – user3770392 Mar 29 '17 at 14:34

1 Answers1

1

The key problem is that by taking the calling argument types directly, and attempting to cast the function pointer, you are losing all implicit conversions.

Your function signature has to match exactly, or you will get UB if you try to call it. And there is generally no way to get the signature from the args without manually specifying it at the call site.

One workaround to try would be to add a wrapper lambda which takes standardized args with pre-specified implicit coversions applied, e.g. T -> const T&, and possibly numeric types -> double.

Then, when you look up the function, you can cast it to use these standardized args, and the calling args will be implicitly converted.

This would rule out functions taking rvalue refs and non-const references, but I don't thing this is unreasonable for a function that you don't know the signature of, unless you want to disregard const-correctness completely.

Also, other implicit conversions wouldn't happen, e.g. Derived& -> Base&, or char* -> std::string, and I don't think there would be an easy way to make that happen without creating extra limitations.

Overall, it's definitely a tricky thing to do in c++, and anything you try will be hacky. This way should be decent enough. The performance overhead of one extra function call (which can be inlined), and possibly some extraneous argument conversions will be overshadowed by the unavoidable RTTI checking.

Here is a sample implementation (also here on ideone):

#include <unordered_map>
#include <typeinfo>
#include <typeindex>
#include <string>
#include <type_traits>
#include <iostream>
#include <assert.h>
#include <cxxabi.h>
#include <sstream>

#include <stdexcept>

template <typename Func, Func f>
struct store_func_helper;

// unix-specific
std::string demangle(const std::string& val) {
  int     status;
  char   *realname;

  std::string strname = realname = abi::__cxa_demangle(val.c_str(), 0, 0, &status);
  free(realname);   
  return strname;
}

// args will be implicitly converted to arg<T>::type before calling function
// default: convert to const Arg&
template <typename Arg, typename snifae=void>
struct arg {
  using type = const Arg&;
};

// numeric types: convert to double.
template <typename Arg>
struct arg <Arg, typename std::enable_if<std::is_arithmetic<Arg>::value, void>::type> {
    using type = double;
};

// set more special arg types here.


// Functions stored in the map are first wrapped in a lambda with this signature.
template <typename Ret, typename... Arg>
using func_type = Ret(*)(typename arg<Arg>::type...);

class func_map {
  template <typename Func, Func f>
  friend class store_func_helper;

public:
template <typename Func, Func f>
void store(const std::string& name){
  store_func_helper<Func, f>::call(this, name );
}

template<typename Ret, typename... Args>
Ret call(std::string func, Args... args){
  using new_func_type = func_type<Ret, Args...>;
  auto& mapVal = m_func_map.at(func);  

  if (mapVal.second != std::type_index(typeid(new_func_type))){
    std::ostringstream ss;
    ss << "Error calling function " << func << ", function type: " 
      << demangle(mapVal.second.name()) 
      << ", attempted to call with " << demangle(typeid(new_func_type).name());
    throw std::runtime_error(ss.str());
  }
  auto typeCastedFun = (new_func_type)(mapVal.first);

  //args will be implicitly converted to match standardized args
  return typeCastedFun(std::forward<Args>(args)...);
};



private:
std::unordered_map<std::string, std::pair<void(*)(),std::type_index> > m_func_map;
};

#define FUNC_MAP_STORE(map, func) (map).store<decltype(&func),&func>(#func);

  template <typename Ret, typename... Args, Ret(*f)(Args...)>
  struct store_func_helper<Ret(*)(Args...), f> {
    static void call (func_map* map, const std::string& name) {
      using new_func_type = func_type<Ret, Args...>;

      // add a wrapper function, which takes standardized args.
      new_func_type lambda = [](typename arg<Args>::type... args) -> Ret {
        return (*f)(args...);
      };
      map->m_func_map.insert(std::make_pair(
        name, 
        std::make_pair((void(*)()) lambda, std::type_index(typeid(lambda)))
        ));
    }
  };

//examples

long add (int i, long j){
  return i + j;
}

int total_size(std::string arg1, const std::string& arg2) {
    return arg1.size() + arg2.size();
}

int  main() {
  func_map map;
  FUNC_MAP_STORE(map, total_size);
  FUNC_MAP_STORE(map, add);

  std::string arg1="hello", arg2="world";
  std::cout << "total_size: "  << map.call<int>("total_size", arg1, arg2) << std::endl;
  std::cout << "add: " << map.call<long>("add", 3, 4) << std::endl;
}
Joseph Ireland
  • 2,465
  • 13
  • 21
  • Note `std::reference_wrapper` could be used to permit *exact* matching of reference types. – Yakk - Adam Nevraumont Mar 29 '17 at 13:20
  • very interesting example. Unfortunately there are the limits you've described and the use of macros that I have already ruled out from the beginning (my fault not mentioning it before, I'll edit the post). Anyway, it can be a valid alternative if you use only some type of functions – user3770392 Mar 29 '17 at 13:38