
While working on assignments for the Computergraphics course I'm taking this semester I was missing signals and slots. Using Qt for the assignments is not possible (well actually I didn't ask explicitly whether I could use it), so I felt challenged to code the convenient things I'm so used to...
I knew that boost had some implementation of signals and slots that used templates but I had never actually taken a look at it. And I didn't want to, yet. I wanted to think about the problem myself first. Basically I came up with that a signal needs to be an object where as a slot may not have to be an object, but must also work as a member function. After a few iterations I came up with a header that now allows me to do the following:
#include "signal.h"
#include <iostream>
struct A
{
void operator()()
{
std::cout << "A::operator()" << std::endl;
}
void f()
{
std::cout << "A::f" << std::endl;
}
};
struct B
{
void operator()(int x)
{
std::cout << "B::operator() " << x << std::endl;
}
void f(int x)
{
std::cout << "B::f " << x << std::endl;
}
};
void g()
{
std::cout << "g" << std::endl;
}
void g(int x)
{
std::cout << "g " << x << std::endl;
}
int main()
{
A a;
Signal<> s;
s.connect(&a);
s.connect(&a, &A::f);
s.connect(&g);
s();
B b;
Signal<int> s2;
s2.connect(&b);
s2.connect(&b, &B::f);
s2.connect(&g);
s2(5);
return 0;
}
The output of that program is:
A::operator()
A::f
g
B::operator() 5
B::f 5
g 5
as you probably expected. Notice that
Now you probably want to see the code for Signal:
I simplified it to work for only zero or one arguments so that it's easier to get an overview. But imagine those
_If<_IsValidArgument >::Result typedefs to span a few lines.
#ifndef SIGNAL_H
#define SIGNAL_H
#include <vector>
class _NoArgument {};
/// undefined result if the condition is neither true nor false
template<bool condition, typename Then, typename Else> struct _If;
/// _If false Else is the Result
template<typename Then, typename Else> struct _If<false, Then, Else> { typedef Else Result; };
/// _If true Then is the Result
template<typename Then, typename Else> struct _If<true, Then, Else> { typedef Then Result; };
/// All arguments are valid
template<typename T> struct _IsValidArgument { enum { Result = true }; };
/// except _NoArgument
template<> struct _IsValidArgument<_NoArgument> { enum { Result = false }; };
struct Slot0 { virtual ~Slot0() {} virtual void invoke() = 0; };
template<typename A1>
struct Slot1 { virtual ~Slot1() {} virtual void invoke(A1) = 0; };
// ...
struct FunctionSlot0Impl : public Slot0
{
typedef void (* FuncPtr)();
inline void invoke() { (*f)(); }
inline FunctionSlot0Impl(FuncPtr _f) : f(_f) {}
FuncPtr f;
};
template<typename A1>
struct FunctionSlot1Impl : public Slot1<A1>
{
typedef void (* FuncPtr)(A1);
inline void invoke(A1 a1) { (*f)(a1); }
inline FunctionSlot1Impl(FuncPtr _f) : f(_f) {}
FuncPtr f;
};
// ...
template<class T>
struct FunctorSlot0Impl : public Slot0
{
inline void invoke() { (*r)(); }
inline FunctorSlot0Impl(T *_r) : r(_r) {}
T *r;
};
template<class T, typename A1>
struct FunctorSlot1Impl : public Slot1<A1>
{
inline void invoke(A1 a1) { (*r)(a1); }
inline FunctorSlot1Impl(T *_r) : r(_r) {}
T *r;
};
// ...
template<class T>
struct MemberSlot0Impl : public Slot0
{
typedef void (T::* FuncPtr)();
inline void invoke() { (r->*f)(); }
inline MemberSlot0Impl(T *_r, FuncPtr _f) : r(_r), f(_f) {}
T *r;
FuncPtr f;
};
template<class T, typename A1>
struct MemberSlot1Impl : public Slot1<A1>
{
typedef void (T::* FuncPtr)(A1);
inline void invoke(A1 a1) { (r->*f)(a1); }
inline MemberSlot1Impl(T *_r, FuncPtr _f) : r(_r), f(_f) {}
T *r;
FuncPtr f;
};
// ...
struct Signal0 {
typedef Slot0 Slot;
inline void operator()() const { emit(); }
inline void emit() const { for (unsigned int i = 0; i < m_slots.size(); ++i) { m_slots[i]->invoke(); } }
std::vector<Slot *> m_slots;
};
template<typename A1>
struct Signal1 {
typedef Slot1<A1> Slot;
inline void operator()(A1 a1) const { emit(a1); }
inline void emit(A1 a1) const { for (unsigned int i = 0; i < m_slots.size(); ++i) { m_slots[i]->invoke(a1); } }
std::vector<Slot *> m_slots;
};
// ...
template<typename A1 = _NoArgument>
class Signal : public _If<_IsValidArgument<A1>::Result,
Signal1<A1>, Signal0>::Result
{
private:
typedef typename _If<_IsValidArgument<A1>::Result,
Signal1<A1>, Signal0>::Result SignalImpl;
typedef typename SignalImpl::Slot SlotObject;
typedef void (* Ptr1)(A1);
typedef void (* Ptr0)();
typedef typename _If<_IsValidArgument<A1>::Result, Ptr1, Ptr0>::Result Ptr;
inline void connectInternal(SlotObject *slot) { SignalImpl::m_slots.push_back(slot); }
public:
template<class Receiver, typename SlotFunc>
inline void connect(Receiver *recv, SlotFunc func)
{
typedef typename _If<_IsValidArgument<A1>::Result,
MemberSlot1Impl<Receiver, A1>,
MemberSlot0Impl<Receiver> >::Result MemberObject;
connectInternal(new MemberObject(recv, func));
}
template<class Receiver>
inline void connect(Receiver *recv)
{
typedef typename _If<_IsValidArgument<A1>::Result,
FunctorSlot1Impl<Receiver, A1>,
FunctorSlot0Impl<Receiver> >::Result FunctorObject;
connectInternal(new FunctorObject(recv));
}
inline void connect(Ptr slot) {
typedef typename _If<_IsValidArgument<A1>::Result,
FunctionSlot1Impl<A1>,
FunctionSlot0Impl>::Result FunctionObject;
connectInternal(new FunctionObject(slot));
}
};
#endif // SIGNAL_H
Now the only thing I'd like to "fix" is the usage of std::vector. Well actually I'm so out of touch with stdlib classes that I have no idea how to use them correctly. I.e. whether to iterate with an iterator instead of an integer is faster (should be), and whether to use a std::list might be more appropriate. Also a disconnect function is still missing...
But this was all just meant to be a little fun learning templates. 