Reputation: 105
I am trying to implement a signal/slot system via templates to get into some advanced usage of templates. I looked up existing implementations and found one at https://github.com/skypjack/eventpp. I have been trying to understand how it works and pretty much figured it out but there are still some things I can't wrap my head around and couldn't find anything on the internet (didn't know what to search for).
Following is an executable minimal example of the code I took from the github repo plus additional code I implemented myself:
//Signal implementation (own code)
template <typename S>
struct Signal
{
using type = S;
};
struct KeyPressedEvent : Signal<KeyPressedEvent>
{
KeyPressedEvent(int keyCode) : keyCode_{keyCode} {}
int keyCode() {return keyCode_;}
private:
int keyCode_;
};
struct KeyReleasedEvent : Signal<KeyReleasedEvent>
{
KeyReleasedEvent(int keyCode) : keyCode_{keyCode} {}
int keyCode() {return keyCode_;}
private:
int keyCode_;
};
//Signal Bus implementation (github repo code)
template<int S, typename... E>
struct BusBase;
template<int S, typename E, typename... O>
struct BusBase<S, E, O...> : BusBase<S, O...>
{
BusBase()
{
if(std::is_same<E, KeyPressedEvent>::value)
{
std::cout << "BusBase<S, E, O...> KeyPressedEvent" << std::endl;
}
if(std::is_same<E, KeyReleasedEvent>::value)
{
std::cout << "BusBase<S, E, O...> KeyReleasedEvent" << std::endl;
}
}
Signal<E> signal_;
};
template<int S>
struct BusBase<S>
{
BusBase()
{
std::cout << "BusBase<S>" << std::endl;
}
};
template <typename... E>
struct Bus : BusBase<sizeof...(E), E...>
{
Bus()
{
std::cout << "Bus" << std::endl;
}
};
int main(int& argc, const char* args[])
{
Bus<KeyPressedEvent, KeyReleasedEvent> eventBus;
std::cin.get();
}
Here is the output I get when I run the program:
BusBase<S>
BusBase<S, E, O...> KeyReleasedEvent
BusBase<S, E, O...> KeyPressedEvent
Bus
I will state what I understood from the implementation. Please correct me if I am wrong.
template<int S, typename... E>
struct BusBase;
is needed to expand the parameter pack from Bus
BusBase<S, E, O...>
is instantiated for every Signal in Bus
BusBase<S>
is needed when the parameter pack has been fully expanded
My questions:
1.
BusBase<S>
Why is this template instantiated first? I would have expected it to be last (before Bus).
2.
template<int S>
struct BusBase<S>
template<int S, typename E, typename... O>
struct BusBase<S, E, O...> : BusBase<S, O...>
Why are the parameters both in the template declarations and in the struct declarations?
Thank you for your help.
Upvotes: 1
Views: 113
Reputation: 217870
Why is this template instantiated first? I would have expected it to be last (before Bus).
constructor call and instantiation are different thing
Bus<KeyPressedEvent, KeyReleasedEvent>
inherits from BusBase<2, KeyPressedEvent, KeyReleasedEvent>
which inherits from BusBase<2, KeyReleasedEvent>
which inherits from BusBase<2>
Call order is from (grand-)parents, members, constructor block, so the outputs you have.
Why are the parameters both in the template declarations and in the struct declarations?
That is the syntax for partial template specialization.
As a note partial specialization is unneeded here and can be rewritten as:
template<int S, typename ... Es>
struct BusBase
{
BusBase()
{
std::cout << "BusBase<S>\n";
(print<Es>(), ...);
}
template <typename T>
void print() const
{
if constexpr (std::is_same<T, KeyPressedEvent>::value) {
std::cout << "BusBase<S, Es...> KeyPressedEvent" << std::endl;
} else if constexpr (std::is_same<T, KeyReleasedEvent>::value) {
std::cout << "BusBase<S, Es...> KeyReleasedEvent" << std::endl;
}
}
std::tuple<Signal<Es>...> signals_;
};
Upvotes: 3