Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems when specializing variadic template member function

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?

like image 756
pic32cpp Avatar asked Jun 13 '26 18:06

pic32cpp


1 Answers

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.

Method 1

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
}

Demo


Method 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
}

Demo

like image 85
Anoop Rana Avatar answered Jun 16 '26 08:06

Anoop Rana