Sono in procinto di refactoring di una classe di grandi dimensioni - chiamiamolo Big
- che ha un'enorme quantità di codice copia-incolla. Gran parte di questo codice copia-incolla esiste in switch
case
s dove solo i tipi coinvolti finiscono per essere diversi. Il codice cambia in base a una variabile membro enum
della classe il cui valore è noto solo in fase di esecuzione.Invio funzione C++ con parametri modello
Il mio tentativo di risolvere questo problema comporta una classe Dispatcher
che ricerca funzioni correttamente digitate tramite una funzione static
denominata lookup()
. Le funzioni che svolgono il lavoro effettivo vengono sempre chiamate go()
e devono essere definite in un modello di classe wrapper (il cui unico parametro è il valore di runtime enum
attualmente attivo). Le funzioni go()
possono essere o non essere funzioni modello stesse.
Ecco una versione distillata del codice. Le mie scuse per la lunghezza, ma questo è stato il più breve che ho potuto ottenere senza perdere il contesto importante.
#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));
}
Questo funziona in gran parte, se non che:
- Esso si basa e funziona bene in Visual C++ 9 (2008), ma sotto GCC 4.8 si traduce in errori di compilazione nella overload function-modello di
lookup()
. - È necessario scrivere un nuovo overload di modello di funzione
lookup()
per ogni nuovo numero di parametri del modello di funzione che si desidera supportare ingo()
. - È scomodo e confuso da usare.
Qui sono gli errori che si verificano sotto 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>;
^
La mia domanda è duplice:
- Perché questa mancanza di costruire sotto GCC, e come posso risolvere il problema?
- C'è un modo migliore (cioè meno ingombrante e confuso) per farlo?
Il codice deve essere compilabile in Visual C++ 9 (2008), quindi non è possibile utilizzare nulla specifico per C++ 11.
Grazie per la risposta. Hai qualche consiglio riguardo la seconda parte della mia domanda? Sebbene questo schema funzioni (grazie al tuo aiuto), non ne sono davvero contento. Ho esplorato usando le funzioni virtuali come alternativa, ma ho colpito un muro di mattoni quando ho capito che questo richiederebbe modelli di funzioni virtuali, che C++ non supporta. – Spire
@Spire: Devo confessare che non mi sono preso il tempo per analizzare il design e cosa fa effettivamente il programma, ho appena individuato questi due errori e ho pensato di pubblicare una risposta. Purtroppo non ho tempo per studiarlo (ho anche dei brutti bug da uccidere nei miei programmi;)) –