Я нахожусь в процессе рефакторинга большого класса — давайте назовем это Big
— у этого есть огромное количество кода копирования-вставки. Большая часть этого кода копирования-вставки существует в switch
case
где только участвующие типы оказываются разными. Код переключается на основе enum
переменная-член класса, значение которого известно только во время выполнения.
Моя попытка исправить это включает в себя наличие Dispatcher
класс, который ищет соответственно типизированные функции через static
функция называется lookup()
, Функции, которые выполняют реальную работу, всегда называются go()
и должны быть определены в шаблоне класса-оболочки (единственным параметром которого является среда выполнения enum
значение в настоящее время включается). go()
функции могут быть или не быть самими шаблонными функциями.
Вот дистиллированная версия кода. Мои извинения за длину, но это было настолько коротким, насколько я мог получить это без потери важного контекста.
#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));
}
Это в основном работает, за исключением того, что:
lookup()
,lookup()
записываться для каждого нового числа параметров шаблонов функций, которые мы хотим поддерживать в go()
,Вот ошибки, которые происходят в 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>;
^
Мой вопрос двоякий:
Код должен быть компилируемым в Visual C ++ 9 (2008), поэтому я не могу использовать ничего специфичного для C ++ 11.
поскольку go
является зависимым именем шаблона, вам нужно использовать template
disambiguator:
case a: return &FunctionWrapper<a>::template go<T>;
// ^^^^^^^^
case b: return &FunctionWrapper<b>::template go<T>;
// ^^^^^^^^
Это говорит компилятору проанализировать, что следует за оператором разрешения контекста (::
) как имя шаблона и последующие угловые скобки в качестве разделителей для аргументов шаблона.
Почему это не удается собрать в GCC, и как мне это исправить?
Потому что GCC соответствует стандарту и выполняет поиск по двухфазному имени, в то время как MSVC задерживает поиск имени до времени создания экземпляра и, следовательно, знает, что go
это имя шаблона.
До создания экземпляра эта информация недоступна, потому что невозможно знать, что T
есть, и основной шаблон может быть специализированным для данного T
чтобы go
это не имя шаблона функции-члена, а скорее элемент данных.
При этом я ожидаю, что MSVC поддержит template
в любом случае, устранение неоднозначности, поэтому добавление его должно заставить вашу программу компилироваться как в GCC / Clang / независимо от соответствия стандарту, так и в MSVC.
Других решений пока нет …