Skip to content

Instantly share code, notes, and snippets.

@elliotpotts
Last active July 12, 2016 09:56
Show Gist options
  • Save elliotpotts/344c27ea7f0cf23c98ba to your computer and use it in GitHub Desktop.
Save elliotpotts/344c27ea7f0cf23c98ba to your computer and use it in GitHub Desktop.
#include <iostream>
#include <type_traits>
#include <typeindex>
#include <map>
#include <vector>
#include <tuple>
#include <utility>
#include <functional>
#include <memory>
#include <stdexcept>
#include <boost/any.hpp>
template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>{
};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
static constexpr auto arity = sizeof...(Args);
typedef ReturnType result_type;
template <size_t i>
using arg = typename std::tuple_element<i, std::tuple<Args...>>::type;
using args = std::tuple<Args...>;
};
template <class ReturnT>
class DynamicFunction {
private:
template<class... ArgsT, size_t... Is>
ReturnT op_call_helper(std::index_sequence<Is...> arg_indices, ArgsT&&... args) {
auto arg_storage = std::make_tuple(args...);
return call({std::get<Is>(arg_storage)...});
}
protected:
virtual ReturnT call(std::vector<boost::any> args) = 0;
public:
template <class... ArgsT>
ReturnT operator()(ArgsT&&... args) {
return op_call_helper(std::index_sequence_for<ArgsT...>(), args...);
}
};
template <class FT>
class DynamicFunctionImpl : public DynamicFunction<typename function_traits<FT>::result_type> {
private:
using ReturnT = typename function_traits<FT>::result_type;
FT f_storage;
template<class... ArgsT, size_t... Is>
ReturnT call_helper(std::vector<boost::any> args, std::index_sequence<Is...> indices) {
auto argument_transport = std::make_tuple(boost::any_cast<typename std::tuple_element<Is, typename function_traits<FT>::args>::type>(args[Is])...);
return f_storage(std::get<Is>(argument_transport)...);
}
protected:
virtual ReturnT call(std::vector<boost::any> args) {
return call_helper(args, std::make_index_sequence<std::tuple_size<typename function_traits<FT>::args>::value>());
}
public:
DynamicFunctionImpl(FT f) : f_storage(f) {
}
};
using runtime_signature = std::vector<std::type_index>;
template <class ReturnT>
class OpenMultiMethod {
private:
std::map<runtime_signature, std::unique_ptr<DynamicFunction<ReturnT>>> methods;
template<class FT, size_t... Is>
void add_method(FT function, std::index_sequence<Is...>) {
runtime_signature args_t = { typeid(typename std::tuple_element<Is, typename function_traits<FT>::args>::type)... };
methods[args_t] = std::unique_ptr<DynamicFunction<ReturnT>>(new DynamicFunctionImpl<FT>(function));
}
public:
template<class FT>
void operator+=(FT function) {
add_method(function, std::make_index_sequence<std::tuple_size<typename function_traits<FT>::args>::value>());
}
template<class... ArgsT>
ReturnT operator()(ArgsT... args) {
runtime_signature signature = { typeid(args)... };
if(methods.find(signature) == methods.end()) {
throw std::runtime_error("Could not match dynamic signature");
} else {
return (*methods[signature])(args...);
}
}
};
class Parent {
virtual void make_vtable() {}
};
struct ChildA : Parent {};
struct ChildB : Parent {};
int main() {
OpenMultiMethod<void> colliding;
colliding += [](ChildA& a, ChildA& b) { std::cout << "AA\n";};
colliding += [](ChildA* a, ChildB* b) { std::cout << "AB\n";};
colliding += [](ChildB* a, ChildA* b) { std::cout << "BA\n";};
colliding += [](ChildB* a, ChildB* b) { std::cout << "BB\n";};
Parent* a = new ChildA();
Parent* b = new ChildB();
colliding(*a, *a);
colliding(a, b);
colliding(b, a);
colliding(b, b);
}
- Allow references
- Allow base pointers
- Allow ordering of suitability of arguments based on inheritance distance & other factors
- Disallow duplicate signature handlers
@falemagn
Copy link

falemagn commented Jul 12, 2016

I tried using this, but as it stand it throws a "Could not match dynamic signature" all the time.

This specific error is due to the fact that OpenMultiMethod::operator() doesn't correctly forward the type of its parameters, losing information.

I believe lines 88 and 89 should look like this:

88. ReturnT operator()(ArgsT&&... args) {`
89.     runtime_signature signature = { typeid(std::forward<ArgsT>(args))... };

With this change it matches the colliding(*a, *a) call (won't match the subsequent ones, though, because of the pointers), but will still fail with this exception:

terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::bad_any_cast> >'
  what():  boost::bad_any_cast: failed conversion using boost::any_cast

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment