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
case
s 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:
- 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()
. - 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 ingo()
. - 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:
- Why is this failing to build under GCC, and how do I fix it?
- 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.