I am experimenting with writing a lightweight signals/observer class that has the following interface (implementation removed for simplification):
template <typename ... ARGS>
class Signal
{
public:
void connect(AbstractSlot<ARGS...>& slot);
void disconnect(AbstractSlot<ARGS...>& slot);
void emit(const ARGS&...args);
//...
};
Which is used, for example, like so:
Signal<> signal1; //A signal with no parameters
Signal<int, int> signal2; //A signal with two int parameters
signal1.emit();
signal2.emit(10, 20);
I want to be able to make my class optionally thread-safe. My thought was to modify my template to accept a "locking policy" class that, by default, is "empty"
struct NoLock
{
void lock() {}
void unlock() {}
};
In my head, I'd like to be able to use the modified Signal template like so:
Signal<> signal1;
Signal<std::mutex> signal1Locked;
Signal<int, int> signal2;
Signal<int, int, std::mutex> signal2Locked;
Which would require me to write the template like so:
template <typename ... ARGS, typename LockPolicy = NoLock>
class Signal;
Except that this isn't valid C++ (and I understand why; how would the compiler know the difference between the policy and the variadic arguments).
My question is, how can I achieve what I desire in a syntax that isn't too confusing/complex to use?
How about
template <typename Signature, typename LockPolicy = NoLock> class Signal;
template <typename... Args, typename LockPolicy>
class Signal<void(Args...), LockPolicy>
{
// ...
};
Signal<void()> signal1;
Signal<void(int, int), std::mutex> signal2;
or use alias (and change order):
template <typename LockPolicy , typename... Args> class LockSignal
{
// ...
};
template <typename... Args>
using Signal = LockSignal<NoLock, Args...>;
Signal<> signal1;
LockSignal<std::mutex, int, int> signal2;