Reputation: 105
I am writing a small signal-slot implementation to learn more about the usage of templates.
I ran into a problem when I want the compiler to deduce the type of a template parameter of a function template from the function's parameter.
Before asking here, I watched several videos (i.e Arthur O'Dwyers 'Template Normal Programming') and read through several articles but i just can't figure out why it isn't working.
I have built a minimal working example:
#include <iostream>
#include <vector>
//Base Event class for passed around events
struct Event
{
virtual ~Event() = default;
Event(int keyCode) : keyCode_{keyCode} {}
int keyCode_;
};
//Event to be passed around
struct KeyPressedEvent final : Event
{
~KeyPressedEvent() { std::cout << "KeyPressedEvent dtor " << std::endl; }
KeyPressedEvent(int keyCode) : Event(keyCode) { std::cout << "KeyPressedEvent param. ctor " << std::endl; }
};
//Class which holds a callback(slot)
struct Window
{
Window(int y) { std::cout << "window param ctor\n"; }
void kpEventHandler(KeyPressedEvent& kpEvent)
{
std::cout << "non-static member staticKpEventHandler() : " << kpEvent.keyCode_ << "\n";
}
};
//Signal which holds connections to callbacks
template<typename Signature> struct Signal;
template<typename Ret, typename ArgType>
struct Signal<Ret(ArgType)>
{
struct Connection
{
using Instance_t = void*;
using Call_t = void(*)(void*, ArgType&&);
//template <typename Type>
Connection(void* instance, Call_t call) :
instance_{instance}, call_{std::move(call)} {}
bool operator==(const Connection& other) const noexcept
{
bool cmpInstance = instance_ == other.instance_;
bool cmpCall = call_ == other.call_;
return cmpInstance && cmpCall;
}
Instance_t instance_;
Call_t call_;
};
std::vector<Connection> connections_;
template<typename C, Ret(C::* func)(ArgType)>
static void call(void* instancePtr, ArgType&& arg)
{
C* instance = static_cast<C*>(instancePtr);
if (instance != nullptr)
{
(instance->*func)(std::forward<ArgType>(arg));
}
}
template<typename C, Ret(C::* func)(ArgType)>
void connect(C& instance)
{
connections_.emplace_back(&instance, std::move(&call<C, func>));
}
template<typename C, Ret(C::*func)(ArgType)>
void disconnect(C& instance)
{
Connection conn{&instance, &call<C, func>};
connections_.erase(std::remove(connections_.begin(), connections_.end(), conn), connections_.end());
}
};
//Test code
int main()
{
{
Window window{5};
Signal<void(KeyPressedEvent&)> signal;
signal.connect<&Window::kpEventHandler>(window); //Error C2974 'Signal<void (KeyPressedEvent &)>::connect': invalid template argument for 'C', type expected
signal.disconnect<&Window::kpEventHandler>(window); //Error C2974 'Signal<void (KeyPressedEvent &)>::disconnect': invalid template argument for 'C', type expected
}
std::cin.get();
return 0;
}
When I change the test code to the following, it obviously works.
signal.connect<Window, &Window::kpEventHandler>(window);
signal.disconnect<Window, &Window::kpEventHandler>(window);
I got it to work when I change Signal::connect()
and Signal::disconnect()
to the following implementation using an auto
template parameter for the function pointer.
template<auto func, typename C>
void connect(C& instance)
{
static_assert(std::is_member_function_pointer<decltype(func)>::value,
"Signal::connect: func is not a pointer to a member function");
connections_.emplace_back(&instance, std::move(&call<C, func>));
}
template<auto func, typename C>
void disconnect(C& instance)
{
static_assert(std::is_member_function_pointer<decltype(func)>::value,
"Signal::disconnect: func is not a pointer to a member function");
Connection conn{&instance, &call<C, func>};
connections_.erase(std::remove(connections_.begin(), connections_.end(), conn), connections_.end());
}
signal.connect<&Window::kpEventHandler>(window); //works fine
signal.disconnect<&Window::kpEventHandler>(window); //works fine
When I change the order of the template parameters in this solution, I get an error as well:
template<typename C, auto func>
void disconnect(C& instance)
{
static_assert(std::is_member_function_pointer<decltype(func)>::value,
"Signal::disconnect: func is not a pointer to a member function");
Connection conn{&instance, &call<C, func>};
connections_.erase(std::remove(connections_.begin(), connections_.end(), conn), connections_.end());
}
signal.disconnect<&Window::kpEventHandler>(window); //Error C2974 'Signal<void (KeyPressedEvent &)>::disconnect': invalid template argument for 'C', type expected
So my questions are:
Signal::connect()
and Signal::disconnect()
implementation to use 'auto func
'?auto func
' solution?Thank you for helping me!
Upvotes: 1
Views: 129
Reputation: 66200
All your questions have the same answer: the order of template parameter is important and, to explicate a template parameter, you have to explicate all template parameters before it.
When you write
template<typename C, Ret(C::* func)(ArgType)>
void connect(C& instance)
or
template<typename C, auto func>
void disconnect(C& instance)
the first template parameter can be deduced from instance
, the second one can't be deduced so has to be explicated.
But, this is the problem, if you have to explicate a template parameter, you must explicate all template parameter before it.
So
signal.connect<&Window::kpEventHandler>(window);
doesn't works because <&Window::kpEventHandler>
explicate the first template parameter, that is C
; and a &Window::kpEventHandler
(that is a value) doesn't matches C
, the first template argument (that has to be a type).
You have to explicate both template parameters, in the correct order, so
signal.connect<Window, &Window::kpEventHandler>(window);
signal.disconnect<Window, &Window::kpEventHandler>(window);
Different if you place the non-deducible template parameter in first position
template<auto func, typename C>
void connect(C& instance)
This way you can explicate the first template parameter (the func
value) and let the compiler to deduce C
from the argument instance
.
So you can write
signal.connect<&Window::kpEventHandler>(window);
signal.disconnect<&Window::kpEventHandler>(window);
but works also if you explicate the second template parameter too
signal.connect<&Window::kpEventHandler, Windows>(window);
signal.disconnect<&Window::kpEventHandler, Windows>(window);
Upvotes: 3