#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;


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;
}
  

