Is there any pattern equivalent to virtual template function?

Viewed 51

I'm working on a monitoring tool and I'm focusing on having low runtime overhead and a simple API. For low-overhead runtime on/off switching I chose to use the state pattern, and to simplify the usage I was going for template parameter packs.

And now for the first time I'm fighting on the line separating compile-time and run-time, and of course can't get a virtual template function to work.

I'm looking for a workaround or an equivalent solution considering the following constraints :

  • Lowest runtime-cost of "disabled" state
  • Flexible API
  • Pre C++17 compliance

And if it's not possible, that's fine too...

Code sample :

static struct /*Disabled*/ State
{
    template <typename... Args>
    /*virtual*/ void operator()(const char * category, Args&&... args) { /*noop*/ }
} 
disabled;

static struct Enabled : State
{
    template <typename... Args>
    /*virtual*/ void operator()(const char * category, Args&&... args) { /*processing category and args*/ }
} 
enabled;

struct Monitor
{
    static State* state;

    template <typename... Args>
    static void write(const char * category, Args&&... args)
    {
        state->operator()(category, std::forward<Args>(args)...);
    }
};

State* Monitor::state = &enabled;

int main()
{
    Monitor::write("BulletCount", 42);
    Monitor::write("Spell", "EnergyShield", true);
}

The code above compiles, but I can never reach the Enabled state without virtual inheritance. Removing the template and having a ton of overloads would do it, but if it can be avoided it could become quite an elegant solution.

2 Answers

You could try a different approach (base on composition istead of inheritance) that maintains the template, like so:

enum state_enum
{
    ENABLED_ST,
    DISABLE_ST
};

struct State
{
    state_enum myState;

    State(state_enum theState):myState(theState)
    {}

    template <typename... Args>
    /*virtual*/ void operator()(const char * category, Args&&... args) 
    { 
        if(myState == DISABLE_ST)
        {
         // noop    
        }
        else if(myState == ENABLED_ST)
        {

        }
    }

};

static State enabled = State(ENABLED_ST);
static State disabled = State(DISABLE_ST);

struct Monitor
{
    static State* state;

    template <typename... Args>
    static void write(const char * category, Args&&... args)
    {
        state->operator()(category, std::forward<Args>(args)...);
    }
};

State* Monitor::state = &enabled;

You don't need any "State", just a boolean "enabled" flag.

struct Monitor
{
    static bool enabled;

    template <typename... Args>
    static void write(const char * category, Args&&... args)
    {
        if (enabled) {
            do_write(category, std::forward<Args>(args)...);
        }
    }

    template <typename... Args>
    static void do_write(const char * category, Args&&... args) { /*processing category and args*/ }

};

bool Monitor::enabled = true;

This saves a level of indirection compared to the virtual methods approach and is faster than the pointers-to-overloads solution due to the static dispatch in combination with a single branch (actually multiple branches all referring to the same variable, though still more pipeline-friendly than dynamic dispatch).

The runtime cost of the disabled state is just a single non-taken branch (and a call to write itself, it it's too big to be inlined). But the branch can't be eliminated if you want to keep the ability to toggle the state at run-time.

Related