2013-05-14 18 views
5

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 switchcase 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:

  1. 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() .
  2. È necessario scrivere un nuovo overload di modello di funzione lookup() per ogni nuovo numero di parametri del modello di funzione che si desidera supportare in go().
  3. È 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:

  1. Perché questa mancanza di costruire sotto GCC, e come posso risolvere il problema?
  2. 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.

risposta

6

Dal go è un nome carico di un modello, è necessario utilizzare il template disambiguatore:

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

Questo dice al compilatore di analizzare quello che segue l'operatore di risoluzione dell'ambito (::) come il nome di un modello e le successive parentesi angolari come delimitatori per gli argomenti del modello.

Perché non sta riuscendo a compilare GCC e come risolverlo?

Perché GCC è conforme allo standard, ed esegue two-phase name lookup, mentre MSVC ritarda ricerca del nome fino al momento di istanze e, quindi, sa che go è il nome di un modello.

Prima istanziazione questa informazione non è disponibile, perché è impossibile sapere che cosa T è, e il modello primario potrebbe essere specializzato per un determinato T in modo che go non è il nome di un modello di funzione membro, ma piuttosto di un membro dei dati.

Detto questo, mi aspetto che MSVC supporti comunque il disambigatore template, quindi l'aggiunta dovrebbe rendere il programma compilato sia su GCC/Clang/whatever-conforms-to-the-Standard che su MSVC.

+0

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

+0

@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;)) –