I have the following state machine example that uses enums to specialize template methods with a variadic parameter pack. Why doesn't the second specialization get called when I pass Properties by reference (Properties&) rather than Properties by value (Properties)?
enum class State
{
StateA,
StateB
};
enum class Event
{
Event1,
Event2
};
struct Properties
{};
template <typename TState, typename TEvent>
struct StateMachine
{
template <TState tstate, TEvent tevent, typename ...TArgs>
void enter(TArgs ...args)
{
std::cout << "Default" << std::endl;
}
};
template <>
template <>
void
StateMachine<State, Event>::enter<State::StateA, Event::Event1>(int i)
{
std::cout << "Specialized 1" << std::endl;
}
template <>
template <>
void
StateMachine<State, Event>::enter<State::StateA, Event::Event2>(Properties& properties)
{
std::cout << "Specialized 2" << std::endl;
}
int main(int argc, char* argv[])
{
StateMachine<State, Event> sm;
int value = 123;
sm.enter<State::StateA, Event::Event1>(value);
Properties properties;
sm.enter<State::StateA, Event::Event2>(properties);
}
I was expecting output:
Specialized 1
Specialized 2
but got:
Specialized 1
Default
When I pass Properties by value, it works correctly. Is this a perfect forwarding problem? How do I fix?
The problem is that only the generic version(aka primary template) takes part in overloading and in your primary template of enter
you have the argument(s) taken by value while in your specialized template for properties the argument is taken by lvalue reference. That is, in the primary template the third argument is deduced as Properties
while for the specialization it is Properties&
.
How do I fix?
This means that there are two way to fix this. You can either args
an lvalue reference or explicitly pass Properties&
as the third argument as shown below.
Here we make args
an lvalue reference to non-const by changing TArgs ...args
to TArgs& ...args
. Note that you can also make it as lvalue reference to const since enter
doesn't change any internals.
template <typename TState, typename TEvent>
struct StateMachine
{
template <TState tstate, TEvent tevent, typename ...TArgs>
//------------------v-------------------->added lvalue ref
void enter(TArgs& ...args)
{
std::cout << "Default" << std::endl;
}
};
template <>
template <>
void
//-----------------------------------------------------------------v-->lvalue ref
StateMachine<State, Event>::enter<State::StateA, Event::Event1>(int& i)
{
std::cout << "Specialized 1" << std::endl;
}
int main(int argc, char* argv[])
{
StateMachine<State, Event> sm;
Properties properties;
sm.enter<State::StateA, Event::Event2>(properties); //prints specialized 2
}
The second way is to explicitly pass the third argument Properties&
.
int main(int argc, char* argv[])
{
StateMachine<State, Event> sm;
Properties properties;
//-----------------------------------------vvvvvvvvvvv--->explicitly pass third argument
sm.enter<State::StateA, Event::Event2, Properties&>(properties); //prints specialized 2
}