Question

I'm in the process of refactoring a large class -- let's call it Big -- that has a huge amount of copy-paste code. Much of this copy-paste code exists in switch cases where only the types involved end up being different. The code is switching based on an enum member variable of the class whose value is known only at runtime.

My attempt to fix this involves having a Dispatcher class that looks up appropriately typed functions via a static function called lookup(). The functions that do the actual work are always called go() and have to be defined in a wrapper class template (whose sole parameter is the runtime enum value currently being switched on). The go() functions may or may not be template functions themselves.

Here is a distilled version of the code. My apologies for the length, but this was as short as I could get it without losing important context.

#include <cassert>

class Big
{
    public:

        enum RuntimeValue { a, b };

        Big(RuntimeValue rv) : _rv(rv) { }

        bool equals(int i1, int i2)
        {
            return Dispatcher<Equals, bool(int, int)>::lookup(_rv)(i1, i2);
        }

        template<typename T>
        bool isConvertibleTo(int i)
        {
            return Dispatcher<IsConvertibleTo, bool(int)>::lookup<T>(_rv)(i);
        }

    private:

        template<RuntimeValue RV>
        struct Equals
        {
            static bool go(int i1, int i2)
            {
                // Pretend that this is some complicated code that relies on RV
                // being a compile-time constant.
                return i1 == i2;
            }
        };

        template<RuntimeValue RV>
        struct IsConvertibleTo
        {
            template<typename T>
            static bool go(int i)
            {
                // Pretend that this is some complicated code that relies on RV
                // being a compile-time constant.
                return static_cast<T>(i) == i;
            }
        };

        template<template<RuntimeValue> class FunctionWrapper, typename Function>
        struct Dispatcher
        {
            static Function * lookup(RuntimeValue rv)
            {
                switch (rv)
                {
                    case a: return &FunctionWrapper<a>::go;
                    case b: return &FunctionWrapper<b>::go;

                    default: assert(false); return 0;
                }
            }

            template<typename T>
            static Function * lookup(RuntimeValue rv)
            {
                switch (rv)
                {
                    case a: return &FunctionWrapper<a>::go<T>;
                    case b: return &FunctionWrapper<b>::go<T>;

                    default: assert(false); return 0;
                }
            }

            // And so on as needed...
            template<typename T1, typename T2>
            static Function * lookup(RuntimeValue rv);
        };

        RuntimeValue _rv;
};

int main()
{
    Big big(Big::a);

    assert(big.equals(3, 3));
    assert(big.isConvertibleTo<char>(123));
}

This mostly works, except that:

  1. It builds and works fine under Visual C++ 9 (2008), but under GCC 4.8 it results in compilation errors in the function-template overload of lookup().
  2. It requires that a new function-template overload of lookup() be written for every new number of function template parameters that we want to support in go().
  3. It's cumbersome and confusing to use.

Here are the errors that occur under GCC:

Big.cpp: In static member function 'static Function* Big::Dispatcher<FunctionWrapper, Function>::lookup(Big::RuntimeValue)':
Big.cpp(66,65) : error: expected primary-expression before '>' token
                         case a: return &FunctionWrapper<a>::go<T>;
                                                                 ^
Big.cpp(66,66) : error: expected primary-expression before ';' token
                         case a: return &FunctionWrapper<a>::go<T>;
                                                                  ^
Big.cpp(67,65) : error: expected primary-expression before '>' token
                         case b: return &FunctionWrapper<b>::go<T>;
                                                                 ^
Big.cpp(67,66) : error: expected primary-expression before ';' token
                         case b: return &FunctionWrapper<b>::go<T>;
                                                                  ^

My question is twofold:

  1. Why is this failing to build under GCC, and how do I fix it?
  2. Is there a better (i.e., less cumbersome and confusing) way to do this?

The code has to be compilable under Visual C++ 9 (2008), so I can't use anything C++11-specific.

Was it helpful?

Solution

Since go is a dependent name of a template, you need to use the template disambiguator:

case a: return &FunctionWrapper<a>::template go<T>;
//                                  ^^^^^^^^
case b: return &FunctionWrapper<b>::template go<T>;
//                                  ^^^^^^^^

This tells the compiler to parse what follows the scope resolution operator (::) as the name of a template, and the subsequent angular brackets as delimiters for the template arguments.

Why is this failing to build under GCC, and how do I fix it?

Because GCC is conforming to the Standard, and performs two-phase name lookup, while MSVC delays name lookup until instantiation time and, therefore, knows that go is the name of a template.

Before instantiation this information is not available, because it is impossible to know what T is, and the primary template could be specialized for a given T so that go is not the name of a member function template, but rather of a data member.

This said, I expect MSVC to support the template disambiguator anyway, so adding it should make your program compile both on GCC/Clang/whatever-conforms-to-the-Standard and on MSVC.

OTHER TIPS

I recently wrote a command dispatcher:

#include <map>

// because std::invoke is not in this compiler version.
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))

template <class MyType, class cmd_type, class ret_type, typename... Args>
class CommandDispatcher {
    typedef ret_type (MyType::*CommandFunction)(Args... args);
public:
    // create using static/existing map
    CommandDispatcher(std::map<cmd_type, CommandFunction>& cmd_map) : _command_table(cmd_map) {}

    ret_type operator()(MyType& my_obj, cmd_type cmd, Args... args) 
    {
        int retval = 0;

        if (_command_table.find(cmd) == _command_table.end()) {
            std::cerr << "No command implementation found: " << cmd << endl;
            return -EINVAL;
        }
        return CALL_MEMBER_FN(my_obj, _command_table[cmd])(args...);
    }
private:
    std::map<cmd_type, CommandFunction>& _command_table;
};

Using it looks like:

class MyClass {
public:
    MyClass() : _dispatcher(_command_map) {}
private:
    static std::map<int,  CommandFunction> _command_map;
    CommandDispatcher<MyClass, int, int, const char*, int> _dispatcher;
};

And in cpp:

std::map<int, CommandFunction> MyClass::_command_map{
    {E_CMD1, &MyClass::Cmd1},
    {E_CMD2, &MyClass::Cmd2},
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top