Newsgroup: it.comp.lang.c++
Titolo: Eliminare gli switch

Salve,
ho una funzione template del tipo:
template<typename T>
T somma(T const& a, T const& b) { return a+b; }
Questa funzione potrà essere chiamata con "int" o "double", ma solo in 
esecuzione saprò il tipo. Quindi sarà chiamata nel seguente modo:
void esegui(int op)
{
   int int_r, int_a, int_b;
   double double_r, double_a, double_b;
   ...codice che inizializza int_a, int_b o double_a, double_b
   switch (op)
   {
     case 0:
       int_r = somma(int_a, int_b);
       break;
     case 1:
       double_r = somma(double_a, double_b);
       break;
   }
   ...codice che usa int_r o double_r
}
Il problema è voglio eliminare lo switch perchè:
- ci saranno molte funzioni del tipo "esegui", e quindi molti switch
- ogni volta che aggiungo un tipo, dovrò aggiungere un case nei vari 
switch, il che potrebbe introdurre errori
Cosa posso fare?

 Cosa posso fare?
Usare la programmazione orientata agli oggetti.
Basta definire una classe astratta contenente un metodo virtuale puro 
"somma", e crea tante classi derivate quanti sono i "tipi". Tali 
funzioni istanziano e richiamano il template di funzione "somma".
Ma se la funzione "somma" e' definita proprio come l'hai scritta, non ne 
capisco l'utilita'. Non bastava usare l'operatore "+" al suo posto? 
Sembra un esempio di pompelmo sintattico (il contrario dello zucchero 
sintattico).
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

 switch, il che potrebbe introdurre errori
Per l'ultimo punto, una pezza rapida è mettere un
default:
        assert(false && "Aggiornare lo switch alla riga __LINE__ di __FILE__");
in fondo a tutti gli switch; è una soluzione non perfetta ma che ti avverte
subito se ti sei dimenticato di aggiornare uno switch.
Per il tuo problema, potresti usare oggetti funzionali, sovraccaricando
l'operatore parentesi:
class Esegui
{
public:
        Esegui();
        ~Esegui();
        void operator()( const int& ) const;
        void operator()( const double& ) const;
private:
        [...]
};
Se, come anticipi, ti trovi molte funzioni del tipo di esegui(), puoi
costruire una semplice gerarchia di funzioni usando una classe genitrice
astratta:
class OperazioneAstratta
{
public:
        OperazioneAstratta();
        ~OperazioneAstratta();
        virtual void operator()( const int& ) const = 0;
        virtual void operator()( const double& ) const = 0;
private:
        [...]
};
Da questa derivi la classe funzionale Esegui e tutte le altre,
specializzando gli operatori di volta in volta.
In questo modo, se vuoi aggiungere un altro tipo (poniamo std::complex), ti
basterà aggiungere 
        virtual void operator( const std::complex& ) const = 0;
E il compilatore si lagnerà finché non avrai implementato tutti i nuovi
operatori delle classi figlie, impedendoti di dimenticare pezzi in giro. Se
ha senso per il tuo programma, ovviamente, potresti anche avere
un'implementazione predefinita dell'operatore.
Il sistema dovrebbe integrarsi bene in funzioni/oggetti template
preesistenti:
template<typename a>
void pinco( const a& arg )
{
        Esegui()(arg);
}
Alla compilazione il template verrà istanziato, e chiamerà l'operator()
giusto.
Potresti anche usare il polimorfismo, se ti fosse utile da qualche parte:
template<typename a>
void pinco( const a& arg, const OperazioneAstratta& op )
{
        op(arg);
}
Ciao,
-Federico
PS: è mezzanotte passata e non ho compilato nulla di tutto ciò... chiedo
venia per eventuali strafalcioni

 sintattico).
la somma è solo un esempio.

 operatori delle classi figlie, impedendoti di dimenticare pezzi in giro. Se...
grazie, questo mi è utile per cercare di evitare errori.

 quale funzione usare, non vedo la differenza).
In effetti non mi e' molto chiaro quanti switch vuoi eliminare, perche' 
dove scrivi:
   ...codice che inizializza int_a, int_b o double_a, double_b
e dove scrivi:
   ...codice che usa int_r o double_r
suppongo che ci siano altrettanti switch.
D'altra parte, dato che la scelta tra i due tipi di operazione deve 
essere fatta a run-time, si deve usare un meccanismo di scelta dinamico, 
tra i quali l'istanziazione dei template non e' compresa, in quanto e' 
un meccanismo statico (compile-time).
I meccanismi di scelta dinamici (se non ne dimentico qualcuno) sono:
- istruzione "if";
- espressione "? :";
- istruzione "switch";
- istruzione "for";
- istruzione "while";
- istruzione "do while";
- chiamata di funzione virtuale.
Scegli quello che preferisci.


p51d ha scritto:
 > Carlo Milanesi ha scritto:
 >> Basta definire una classe astratta contenente un metodo virtuale 
puro "somma", e crea tante classi derivate quanti sono i "tipi". Tali 
funzioni istanziano e richiamano il template di funzione "somma".
 >
 > non vedo come questo possa comunque eliminare lo "switch". Nel senso 
che sarà l'utente che usa l'applicazione a decidere quale classe usare 
(o quale funzione usare, non vedo la differenza).
In effetti non mi e' molto chiaro quanti switch vuoi eliminare, perche' 
dove scrivi:
   ...codice che inizializza int_a, int_b o double_a, double_b
e dove scrivi:
   ...codice che usa int_r o double_r
suppongo che ci siano altrettanti switch.
D'altra parte, dato che la scelta tra i due tipi di operazione deve 
essere fatta a run-time, si deve usare un meccanismo di scelta dinamico, 
tra i quali l'istanziazione dei template non e' compresa, in quanto e' 
un meccanismo statico (compile-time).
I meccanismi di scelta dinamici (se non ne dimentico qualcuno) sono:
- istruzione "if";
- espressione "? :";
- istruzione "switch";
- istruzione "for";
- istruzione "while";
- istruzione "do while";
- chiamata di funzione virtuale;
- jump table (chiamato anche "goto calcolato", solo in assembly o in 
altri linguaggi);
- collezione di puntatori a funzione;
- operatore "dynamic_cast" (che pero' e' praticamente inutile senza 
un'istruzione "if" o equivalente);
- operatore "throw";
- funzione "longjmp".
Scegli quello che preferisci.


Salve,
  il tuo è un problema di design. Quello è codice C, bello e buono. 
Programmare in C++ è un pò diverso come approccio mentale. Dovresti Avere 
una serie di classi che incapsulano anche i dati. E' questo che ti manca. 
Saranno function object e la tua funzione esegui andrà templetizzata con 
i parametri da passare al template
 
void esegui<t1,t2>() {
  s = Somma<t1,t2>(a,b);
....
}
Oppure esasperando l'uso dei template, se hai più operazioni, anche essa 
diventerà un parametro della classe, ovvero sarà una policy class.
cordialmente,
  nelson

 }
il problema è che t1 e t2 saranno conosciuti a runtime.



Beh una factory realizzata con i template ed un pò di programmazione 
generica con funciton objects dovrebbe fare al caso suo. Usare cose come 
union e altre amenità C style è NON programmare ad oggetti.
Per quel chemi riguarda potrebbe benissimo essere anche un problema di 
design. Che dovrà fare questa libraria? che concetti implementa?
cordialmente

 il problema è che t1 e t2 saranno conosciuti a runtime.
Dunque tu hai N tipi di dati e devi implementare una funzione binaria 
che accetti tutti questi tipi, giusto? Cioè hai N^2 diverse funzioni per 
ogni operazione che devi svolgere. Se devi affrontare il problema in 
questa generalità l'unico modo è che ogni tipo T abbia una funzione
virtual Object T::eseguiOp(Object r);
che sia in grado di eseguire la somma di sè stesso con un oggetto r di 
qualunque altro tipo. Questa in generale è l'unica soluzione possibile, 
ma se hai qualche informazione in più la puoi (anzi devi) sfruttare. Ad 
esempio se ci fosse una gerarchia naturale di oggetti, potresti chiedere 
  ad ogni oggetto di definire le proprie operazioni solo con gli oggetti 
che vengono prima di lui nella gerarchia. Se poi l'operazione è 
commutativa dovresti fare in modo di ordinare i due operandi, in modo da 
dimezzare il numero di funzioni da implementare.
E.