I've written the following implementation for a generic signal/slot system:
template< typename... Args >
class Signal : NonCopyable
{
public:
typedef std::function< void (Args...) > Delegate;
void connect(const Delegate& delegate);
void operator ()(Args&&... args) const;
private:
std::list< Delegate > _delegates;
};
template< typename... Args >
void Signal< Args... >::connect(const Delegate& delegate)
{
_delegates.push_front(delegate);
}
template< typename... Args >
void Signal< Args... >::operator ()(Args&&... args) const
{
for (const Delegate& delegate : _delegates)
delegate(std::forward< Args >(args)...);
}
Afterwards, I tested my class using the following simple cases:
Signal< int > signal;
// Case 1
signal(0);
//Case 2
int i(0);
signal(i);
Case 1 compiles without issue. Case 2, on the other hand, generates the following error under GCC 4.7.2:
/home/pmjobin/Workspace/main.cpp:1196:10: error: cannot bind ‘int’ lvalue to ‘int&&’
/home/pmjobin/Workspace/main.cpp:82:6: error: initializing argument 1 of ‘void Signal<Args>::operator()(Args&& ...) const [with Args = {int}]’
I understand the issue has something to do with perfect-forwarding (and my misunderstanding of the latter). However, I've inspired myself from the std::make_shared() & std::make_tuple() implementations and I fail to see any difference in the way I forward the variadic arguments to the delegates. The only notable difference being that both make_shared() and make_tuple() are function templates rather than a class template such as the Signal implementation above.
- EDIT -
In response to the various comments, here is a new version of the Signal class implementation which doesn't suffer from the aforementioned issues. Additionally, it is now possible to disconnect delegates using an opaque token returned by the connect function. The result might not be as flexible and powerful as other implementations out there (such as boost::signal), but at least, it has the benefit of being simple and lightweight.
template< typename Signature >
class Signal : NonCopyable
{
public:
typedef std::function< Signature > Delegate;
class DisconnectionToken
{
DisconnectionToken(typename std::list< Delegate >::iterator it)
: _it(it)
{}
typename std::list< Delegate >::iterator _it;
friend class Signal;
};
DisconnectionToken connect(const Delegate& delegate);
void disconnect(DisconnectionToken& token);
template< typename... Args >
void operator ()(Args&&... args) const;
private:
std::list< Delegate > _delegates;
};
template< typename Signature >
typename Signal< Signature >::DisconnectionToken Signal< Signature >::connect(const Delegate& delegate)
{
_delegates.push_front(delegate);
return DisconnectionToken(_delegates.begin());
}
template< typename Signature >
void Signal< Signature >::disconnect(DisconnectionToken& token)
{
if (token._it != _delegates.end())
{
_delegates.erase(token._it);
token._it = _delegates.end();
}
}
template< typename Signature >
template< typename... Args >
void Signal< Signature >::operator ()(Args&&... args) const
{
for (const Delegate& delegate : _delegates)
delegate(std::forward< Args >(args)...);
}
Args&&... args
is basicallyint && args
. - NawazArgs...
for theoperator()
. - Seth Carnegiestd::function
would not go along very well. - NawazX&&
andX const&
(for example). - Nawaz