Created
August 17, 2014 10:41
-
-
Save Rapptz/8fc1b71f86dc8ae49492 to your computer and use it in GitHub Desktop.
Signal and Slots C++11
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
// C++11 signal/slots | |
#include <type_traits> | |
#include <functional> | |
#include <map> | |
#include <limits> | |
namespace signals { | |
struct connection_key { | |
private: | |
connection_key() = default; | |
public: | |
template<typename T, typename U, typename V> friend struct signal; | |
}; | |
template<bool B> | |
using Enabler = typename std::enable_if<B, int>::type; | |
template<typename Function, typename Signature> | |
struct is_callable : std::false_type { | |
static_assert(std::is_function<Signature>::value, "Signature must be a function type"); | |
}; | |
struct is_callable_impl { | |
template<typename Function, typename Return, typename... Args, | |
typename R = decltype(std::declval<Function>()(std::declval<Args>()...))> | |
static std::is_same<Return, R> test(int); | |
template<typename...> | |
static std::false_type test(...); | |
}; | |
template<typename Function, typename Return, typename... Args> | |
struct is_callable<Function, Return(Args...)> : public decltype(is_callable_impl::test<Function, Return, Args...>(0)) {}; | |
template<typename Signature> | |
struct connection { | |
static_assert(std::is_function<Signature>::value, "connection expects a function signature"); | |
using function_type = std::function<Signature>; | |
private: | |
bool enabled_ = true; | |
function_type callback; | |
public: | |
template<typename Function, Enabler<!std::is_same<typename std::decay<Function>::type, connection>::value> = 0> | |
connection(Function&& f): callback(f) {} | |
connection(const connection&) = default; | |
connection(connection&&) = default; | |
template<typename... Args> | |
auto invoke(connection_key, const Args&... args) -> decltype(callback(args...)) { | |
return callback(args...); | |
} | |
void disable() { | |
enabled_ = false; | |
} | |
void enable() { | |
enabled_ = true; | |
} | |
bool enabled() const noexcept { | |
return enabled_ && static_cast<bool>(callback); | |
} | |
void disconnect() { | |
callback = nullptr; | |
} | |
}; | |
struct default_compare { | |
template<typename T, Enabler<!std::is_enum<T>::value> = 0> | |
bool operator()(const T& lhs, const T& rhs) const noexcept { | |
return lhs < rhs; | |
} | |
template<typename T, Enabler<std::is_enum<T>::value> = 0> | |
bool operator()(const T& lhs, const T& rhs) const noexcept { | |
using U = typename std::underlying_type<T>::type; | |
return static_cast<U>(lhs) < static_cast<U>(rhs); | |
} | |
}; | |
template<typename Signature> | |
struct return_type {}; | |
template<typename Return, typename... Args> | |
struct return_type<Return(Args...)> { | |
using type = Return; | |
}; | |
template<typename Group> | |
struct default_group { | |
private: | |
struct id { using type = Group; }; | |
using integral_type = typename std::conditional<std::is_enum<Group>::value, std::underlying_type<Group>, id>::type; | |
public: | |
using value_type = typename integral_type::type; | |
static constexpr Group value = static_cast<Group>(std::numeric_limits<value_type>::max()); | |
}; | |
template<typename Signature, typename Group = int, typename Compare = default_compare> | |
struct signal { | |
static_assert(std::is_function<Signature>::value, "signal expects a function signature"); | |
using connection_type = connection<Signature>; | |
using connection_reference = connection_type&; | |
using result_type = typename return_type<Signature>::type; | |
using slot_type = typename connection_type::function_type; | |
private: | |
std::multimap<Group, connection_type, Compare> connections; | |
public: | |
signal() = default; | |
template<typename Function> | |
connection_reference connect(Function&& f) { | |
return connect(default_group<Group>::value, std::forward<Function>(f)); | |
} | |
template<typename Function> | |
connection_reference connect(Group g, Function&& f) { | |
auto&& c = connections.emplace(g, std::forward<Function>(f)); | |
return c->second; | |
} | |
template<typename... Args, typename U = result_type, Enabler<!std::is_void<U>::value> = 0> | |
result_type emit_to(Group g, const Args&... args) const { | |
auto&& r = connections.equal_range(g); | |
result_type result; | |
for(; r.first != r.second; ++r.first) { | |
auto&& f = r.first->second; | |
if(f.enabled()) { | |
result = f.invoke(connection_key{}, args...); | |
} | |
} | |
return result; | |
} | |
template<typename Callback, typename... Args, typename U = result_type, | |
Enabler<!std::is_void<U>::value and is_callable<Callback, void(U)>::value> = 0> | |
void emit_to(Group g, Callback callback, const Args&... args) const { | |
auto&& r = connections.equal_range(g); | |
for(; r.first != r.second; ++r.first) { | |
auto&& f = r.first->second; | |
if(f.enabled()) { | |
callback(f.invoke(connection_key{}, args...)); | |
} | |
} | |
} | |
template<typename... Args, typename U = result_type, Enabler<std::is_void<U>::value> = 0> | |
result_type emit_to(Group g, const Args&... args) { | |
auto&& r = connections.equal_range(g); | |
for(; r.first != r.second; ++r.first) { | |
auto&& f = r.first->second; | |
if(f.enabled()) { | |
f.invoke(connection_key{}, args...); | |
} | |
} | |
} | |
template<typename... Args, typename U = result_type, Enabler<!std::is_void<U>::value> = 0> | |
result_type emit(const Args&... args) { | |
result_type result; | |
for(auto&& p : connections) { | |
auto&& f = p.second; | |
if(f.enabled()) { | |
result = f.invoke(connection_key{}, args...); | |
} | |
} | |
return result; | |
} | |
template<typename Callback, typename... Args, typename U = result_type, | |
Enabler<!std::is_void<U>::value and is_callable<Callback, void(U)>::value> = 0> | |
void emit(Callback callback, const Args&... args) { | |
for(auto&& p : connections) { | |
auto&& f = p.second; | |
if(f.enabled()) { | |
callback(f.invoke(connection_key{}, args...)); | |
} | |
} | |
} | |
template<typename... Args, typename U = result_type, Enabler<std::is_void<U>::value> = 0> | |
result_type emit(const Args&... args) { | |
for(auto&& p : connections) { | |
auto&& f = p.second; | |
if(f.enabled()) { | |
f.invoke(connection_key{}, args...); | |
} | |
} | |
} | |
}; | |
} // signals | |
#include <iostream> | |
class Button | |
{ | |
typedef signals::signal<void(int x, int y)> OnClick; | |
public: | |
typedef OnClick::slot_type OnClickSlotType; | |
OnClick::connection_type doOnClick(const OnClickSlotType& slot) { | |
return onClick.connect(slot); | |
} | |
void simulateClick() { | |
onClick.emit(52, 38); | |
} | |
private: | |
OnClick onClick; | |
}; | |
void printCoordinates(long x, long y) | |
{ | |
std::cout << "(" << x << ", " << y << ")\n"; | |
} | |
int main() { | |
Button button; | |
button.doOnClick(printCoordinates); | |
button.simulateClick(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment