Created
November 9, 2017 13:54
-
-
Save muggenhor/91db73946714b91f3fbbbaefdf743148 to your computer and use it in GitHub Desktop.
std::function variant adapted for callbacks with support for reference counted receivers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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