Skip to content

Instantly share code, notes, and snippets.

@2bbb
Last active March 8, 2018 19:25
Show Gist options
  • Save 2bbb/cb2e2c0831a9fc8283ee1c887e8eb903 to your computer and use it in GitHub Desktop.
Save 2bbb/cb2e2c0831a9fc8283ee1c887e8eb903 to your computer and use it in GitHub Desktop.
polyfunction
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <functional>
namespace bbb {
namespace {
template <bool b, typename type>
using enable_if_t = typename std::enable_if<b, type>::type;
};
inline namespace function_traits {
template <typename>
struct is_function
: std::false_type {};
template <typename res, typename ... arguments>
struct is_function<std::function<res(arguments ...)>>
: std::false_type {};
template <std::size_t index, typename ... arguments>
using type_at_t = typename std::tuple_element<index, std::tuple<arguments ...>>::type;
template <typename patient>
struct has_call_operator {
template <typename inner_patient, decltype(&inner_patient::operator())> struct checker {};
template <typename inner_patient> static std::true_type check(checker<inner_patient, &inner_patient::operator()> *);
template <typename> static std::false_type check(...);
using type = decltype(check<patient>(nullptr));
static constexpr bool value = type::value;
};
namespace detail {
template <typename ret, typename ... arguments>
struct function_traits {
static constexpr std::size_t arity = sizeof...(arguments);
using result_type = ret;
using arguments_types_tuple = std::tuple<arguments ...>;
template <std::size_t index>
using argument_type = type_at_t<index, arguments ...>;
using function_type = std::function<ret(arguments ...)>;
template <typename function_t>
static constexpr function_type cast(function_t f) {
return static_cast<function_type>(f);
}
};
};
template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())> {};
template <typename class_type, typename ret, typename ... arguments>
struct function_traits<ret(class_type::*)(arguments ...) const>
: detail::function_traits<ret, arguments ...> {};
template <typename class_type, typename ret, typename ... arguments>
struct function_traits<ret(class_type::*)(arguments ...)>
: detail::function_traits<ret, arguments ...> {};
template <typename ret, typename ... arguments>
struct function_traits<ret(*)(arguments ...)>
: detail::function_traits<ret, arguments ...> {};
template <typename ret, typename ... arguments>
struct function_traits<ret(arguments ...)>
: detail::function_traits<ret, arguments ...> {};
template <typename ret, typename ... arguments>
struct function_traits<std::function<ret(arguments ...)>>
: detail::function_traits<ret, arguments ...> {};
};
template <typename function_type>
struct polyfunction;
template <typename res, typename ... arguments>
struct polyfunction<res(arguments ...)> {
using function_type = std::function<res(arguments ...)>;
polyfunction()
: f([](arguments ...) -> res { return {}; }) {};
polyfunction(const polyfunction &) = default;
polyfunction(polyfunction &&) = default;
polyfunction(const function_type &f)
: f(f) {};
polyfunction(function_type &&f)
: f(std::move(f)) {};
template <typename res_, typename ... arguments_>
polyfunction(const std::function<res_(arguments_ ...)> &f_)
: f(polyfunction::convert(f_, std::make_index_sequence<sizeof...(arguments_)>()))
{};
template <typename res_, typename ... arguments_>
polyfunction(std::function<res_(arguments_ ...)> &&f_)
: f(polyfunction::convert(std::move(f_), std::make_index_sequence<sizeof...(arguments_)>()))
{};
template <
typename function_type,
typename = typename std::enable_if<
!is_function<function_type>::value &&
has_call_operator<function_type>::value
>::type
>
polyfunction(function_type f)
: polyfunction(function_traits<function_type>::cast(f)){};
polyfunction &operator=(const polyfunction &) = default;
polyfunction &operator=(polyfunction &&) = default;
polyfunction &operator=(const function_type &f) { this->f = f; return *this; };
polyfunction &operator=(function_type &&f) { this->f = std::move(f); return *this; };
template <typename function_type>
auto operator=(function_type f)
-> typename std::enable_if<
!is_function<function_type>::value &&
has_call_operator<function_type>::value,
polyfunction &
>::type
{ return operator=(function_traits<function_type>::cast(f)); };
template <typename res_, typename ... arguments_>
polyfunction &operator=(const std::function<res_(arguments_ ...)> &f) {
this->f = polyfunction::convert(f, std::make_index_sequence<sizeof...(arguments_)>());
return *this;
};
template <typename res_, typename ... arguments_>
polyfunction &operator=(std::function<res_(arguments_ ...)> &&f) {
this->f = polyfunction::convert(std::move(f), std::make_index_sequence<sizeof...(arguments_)>());
return *this;
};
template <typename ... other_arguments>
res operator()(arguments ... args, other_arguments && ...) const { return f(std::forward<arguments>(args) ...); };
template <typename ... other_arguments>
operator std::function<res(arguments ..., other_arguments && ...)>() const {
auto &&f_ = f;
return [f_](arguments ... args, other_arguments && ...) -> res { return f_(args ...); };
};
template <typename function_type_>
std::function<function_type_> as() const { return std::function<function_type_>(*this); };
private:
template <typename res_, typename ... arguments_, std::size_t ... indices>
static auto convert(std::function<res_(arguments_ ...)> f, std::index_sequence<indices ...> &&)
-> enable_if_t<sizeof...(arguments_) <= sizeof...(arguments), function_type>
{
return [f](arguments ... args) {
return static_cast<res>(f(get<indices>(std::forward<arguments>(args) ...) ...));
};
};
template <std::size_t index>
static auto get(arguments ... args)
-> typename std::tuple_element<index, std::tuple<arguments ...>>::type
{ return std::get<index>(std::tuple<arguments ...>(std::forward<arguments>(args) ...)); };
function_type f;
};
};
#include <iostream>
int main(int argc, char *argv[]) {
{
std::function<int(int)> g = [](int x) { return x * x; };
std::function<float()> h = [] { return 17.2f; };
bbb::polyfunction<int(int, int)> f1(g);
bbb::polyfunction<int(int, int)> f2(h);
std::cout << f1(4, 2) << std::endl;
std::cout << f2(4, 2) << std::endl;
}
{
std::function<void(std::string)> g = [](std::string a) { std::cout << a << std::endl; };
bbb::polyfunction<void(const std::string &, int)> f(g);
bbb::polyfunction<void(const std::string &, int)> f_([](std::string a) { std::cout << a << std::endl; });
f_ = []() { std::cout << "p" << std::endl; };
f_("a", 4);
f("a", 4);
decltype(f) f2;
f2("a", 4);
std::function<void(const std::string &, int, float, long)> h = f;
f.as<void(const std::string &, int, int)>()("abc", 1, 2);
std::function<void(const std::string &, int, int)> p = f;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment