Skip to content

Instantly share code, notes, and snippets.

@muggenhor
Created November 9, 2017 13:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save muggenhor/91db73946714b91f3fbbbaefdf743148 to your computer and use it in GitHub Desktop.
Save muggenhor/91db73946714b91f3fbbbaefdf743148 to your computer and use it in GitHub Desktop.
std::function variant adapted for callbacks with support for reference counted receivers
#include <cstddef>
#include <functional>
#include <memory>
#include <type_traits>
#include <utility>
namespace detail
{
template <typename...>
struct void_t_helper
{
using type = void;
};
template <typename... Args>
using void_t = typename void_t_helper<Args...>::type;
template <typename Result, typename Ret, typename = void>
struct __is_callable_impl : std::false_type {};
template <typename Result, typename Ret>
struct __is_callable_impl<Result, Ret, void_t<typename Result::type>>
: std::true_type
{ };
template <typename F, typename... Args>
struct is_callable
: __is_callable_impl<std::result_of<F(Args...)>, void>::type
{ };
template <typename... Args>
struct callback_helper
{
template <typename R, typename I, typename F>
typename std::enable_if<is_callable<F, Args...>::value, R>::type
static do_invoke(I&&, F&& f, Args... args)
{
return static_cast<R>(std::forward<F>(f)(std::forward<Args>(args)...));
}
template <typename R, typename I, typename F>
typename std::enable_if<is_callable<F, I, Args...>::value, R>::type
static do_invoke(I&& that, F&& f, Args... args)
{
return static_cast<R>(std::forward<F>(f)(std::forward<I>(that), std::forward<Args>(args)...));
}
template <typename R, typename FR, typename I>
static R do_invoke(I* that, FR I::* f, Args... args)
{
return static_cast<R>((that->*f)(std::forward<Args>(args)...));
}
};
template <typename R, typename... Args>
struct callback_base
{
virtual ~callback_base() noexcept = default;
virtual R invoke(Args... args) const = 0;
virtual bool is_valid() const noexcept = 0;
virtual std::unique_ptr<callback_base> clone() const = 0;
};
struct always_valid_ptr
{
constexpr const always_valid_ptr* lock() const noexcept
{
return this;
}
};
template <typename LockablePtr, typename F, typename R, typename... Args>
class callback_impl final : public callback_base<R, Args...>
// inherit for empty-base optimisation
, private LockablePtr
{
private:
using base_t = callback_base<R, Args...>;
public:
callback_impl(LockablePtr p, F f)
: LockablePtr(std::move(p))
, f(std::move(f))
{}
R invoke(Args... args) const override
{
if (auto i = LockablePtr::lock())
{
return callback_helper<Args...>::template do_invoke<R>(&*i, f, std::forward<Args>(args)...);
}
else
{
throw std::bad_function_call();
}
}
bool is_valid() const noexcept override
{
return static_cast<bool>(LockablePtr::lock()) && static_cast<bool>(f);
}
std::unique_ptr<base_t> clone() const override
{
return std::unique_ptr<base_t>{ new callback_impl(*this) };
}
private:
F f;
};
}
template <typename FunctionSignature>
class callback;
template <typename R, typename... Args>
class callback<R(Args...)>
{
public:
constexpr callback() noexcept = default;
constexpr callback(std::nullptr_t) noexcept {}
callback(const callback& other)
: impl(other.impl ? other.impl->clone() : nullptr)
{}
callback(callback&& other) noexcept = default;
template <typename F
, typename = typename std::enable_if<!std::is_same<F, callback>::value>::type
, typename = typename std::enable_if<
std::is_convertible<typename std::result_of<F(Args...)>::type, R>::value
|| std::is_void<R>::value
>::type>
callback(F f)
: impl(new detail::callback_impl<detail::always_valid_ptr, F, R, Args...>({}, std::move(f)))
{
}
template <typename LockablePtr, typename F
, typename = typename std::enable_if<!std::is_same<F, callback>::value &&
(std::is_convertible<typename std::result_of<F(Args...)>::type, R>::value
|| std::is_void<R>::value)
>::type>
callback(LockablePtr p, F f)
: impl(p.lock()
? new detail::callback_impl<LockablePtr, F, R, Args...>(std::move(p), std::move(f))
: nullptr)
{
}
template <typename LockablePtr, typename F>
callback(LockablePtr p, F f
, typename std::enable_if<!std::is_same<F, callback>::value &&
(std::is_convertible<typename std::result_of<F(typename LockablePtr::element_type*, Args...)>::type, R>::value
|| std::is_void<R>::value)
>::type* = nullptr)
: impl(p.lock()
? new detail::callback_impl<LockablePtr, F, R, Args...>(std::move(p), std::move(f))
: nullptr)
{
}
friend void swap(callback& lhs, callback rhs) noexcept
{
swap(lhs.impl, rhs.impl);
}
callback& operator=(callback rhs)
{
swap(*this, rhs);
return *this;
}
callback& operator=(std::nullptr_t)
{
impl.reset();
return *this;
}
explicit operator bool() const noexcept
{
return impl && impl->is_valid();
}
R operator()(Args... args) const
{
if (!*this)
throw std::bad_function_call();
return impl->invoke(std::forward<Args>(args)...);
}
private:
std::unique_ptr<detail::callback_base<R, Args...>> impl;
};
#ifdef TEST
#include <cassert>
#include <iostream>
struct cb_rcvr
{
constexpr cb_rcvr(int z) : z(z) {}
int f(int x, char y)
{
std::cout << __PRETTY_FUNCTION__ << " called with z=" << this->z << " and (" << x << ",'" << y << "')\n";
return 21;
}
int z;
};
int some_f(int x, char y)
{
std::cout << __PRETTY_FUNCTION__ << " called with (" << x << ",'" << y << "')\n";
return 42;
}
int some_g(cb_rcvr* that, int x, char y)
{
std::cout << __PRETTY_FUNCTION__ << " called with z=" << that->z << " and (" << x << ",'" << y << "')\n";
return 63;
}
int main()
{
callback<void (int, char)> x(some_f);
callback<void (int, char)> y{x};
callback<void (int, char)> z{std::move(x)};
auto t = std::make_shared<cb_rcvr>(1337);
callback<int (int, char)> w{std::weak_ptr<cb_rcvr>{t}, &cb_rcvr::f};
callback<int (int, char)> v{std::weak_ptr<cb_rcvr>{t}, some_g};
y(0, 'a');
z(1, 'b');
std::cout << w(2, 'c') << " returned\n";
std::cout << v(3, 'd') << " returned\n";
try
{
assert(!x);
x(4, 'e');
assert(!"expected an std::bad_function_call exception!");
}
catch (std::bad_function_call const&)
{
}
t.reset();
assert(!w);
assert(!v);
try
{
w(5, 'f');
assert(!"expected an std::bad_function_call exception!");
}
catch (std::bad_function_call const&)
{
}
try
{
v(6, 'g');
assert(!"expected an std::bad_function_call exception!");
}
catch (std::bad_function_call const&)
{
}
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment