Newsgroup: it.comp.lang.c++
Titolo: Template e serializzazione

Hi all
il problema salta fuori perché sto cercando di mixare run e compile time. Per 
cominciare vedo di spiegarmi:
Ho una struct templatizzata, che a seconda del valore del template implementa 
un operatore differente.
enum COMPOSITION_OPERATOR
{
	ADDITION,
	SUBTRACTION
};
template<COMPOSITION_OPERATOR T>
struct CompositionOperator;
template<>
struct CompositionOperator<ADDITION>
{
	static const COMPOSITION_OPERATOR Operator = ADDITION;
	float operator() (float x, float y)
	{
		return x+y;
	}
};
stessa cosa per SUBTRACTION.
Il problema si presenta quando devo serializzare l'operatore.
Posso salvare la proprietà Operator, che in fin dei conti é un intero, ma non 
saprei come fare a ricreare un'istanza della struct.
Posso rileggere l'intero, ma come fare a istanziare una struct a partire da 
una variabile locale?
Mi vengono in mente varie possibili soluzioni, ma tutte richiedono un po' di 
lavoro a runtime (come mantenere un "registro" degli operatori disponibili). 
Speravo che ci fosse una soluzione pulita basata fondamentalmente su template.
Se qualcuno mi può aiutare gliene sono grato :-)
-- 
         Giuseppe "Cowo" Corbelli ~\/~ My software: 
http://cowo.yoda2000.net
-<! Non c'e' niente da dire in proposito. Tutto quello che uno deve fare e'
   colpire i tasti giusti al momento giusto, e lo strumento suona da solo. !>-
                                 J.S. Bach

 Il problema si presenta quando devo serializzare l'operatore.
Cosa intendi per "serializzare l'operatore"?
Kiuhnm

 Cosa intendi per "serializzare l'operatore"?
Salvare in modo persistente un'istanza della struttura, ad esempio su un
file, e ricreare un'istanza della stessa a partire dai dati letti da file.
Uso boost::serialization, ma il problema che ho esposto non c'entra con la
libreria usata.
-- 
  Giuseppe "Cowo" Corbelli \/\ My software: 
http://cowo.yoda2000.net
    **ANTI-SPAM** -- **Substitute IHATESPAM with cowo to reply by mail**
  -<! A mathematician is a device for turning coffee into theorems. !>-
                P. Erdos

 libreria usata.
Su file potrai salvare il contenuto di una "classe" (intesa in senso 
generale), non la sua definizione. Solo il compilatore può lavorare 
direttamente con le definizioni.
Le istanze relative ai template vengono create in compile-time, non in 
run-time. Pensa ai template come a macro molto sofisticate. Ti 
sogneresti mai di salvare una macro su file? Non puoi pretendere di 
salvare il parametro di un template e di creare l'istanza in run-time.
Ruby permette, da quanto ho sentito, di fare cose del genere, ma è 
lentissimo.
Kiuhnm


Pero' salvando il suo id numerico puo' attraverso una factory scegliere
la classe, ovvero la specializzazione del template, da istanziare. Il
problema e' che questo approccio richiede per forza di cose una classe
base comune e se cio' che fa lo fa per realizzare qualcosa tipo
expression templates ha perso in partenza.
Ciao,
Nicola

 la classe, ovvero la specializzazione del template, da istanziare. Il
No, non può.
Non si può creare un'istanza in run-time.
Kiuhnm


Forse non sono stato chiarissimo: le specializzazioni del template
devono essere gia' tutte istanziate. Quello che puo' fare e' scegliere
da quale di queste istanziare un oggetto. Qualcosa come:
enum COMPOSITION_OPERATOR {
        ADDITION,
        SUBTRACTION
};
template<COMPOSITION_OPERATOR T>
struct CompositionOperator;
class AbstractCompositionOperator {
  public:
    virtual float operator() (float x, float y) = 0;
    virtual ~AbstractCompositionOperator() {}
};
template<>
struct CompositionOperator<ADDITION> : public
AbstractCompositionOperator
{
        static const COMPOSITION_OPERATOR Operator = ADDITION;
        float operator() (float x, float y)
        {
                return x+y;
        }
};
template<>
struct CompositionOperator<SUBTRACTION> : public
AbstractCompositionOperator
{
        static const COMPOSITION_OPERATOR Operator = SUBTRACTION;
        float operator() (float x, float y)
        {
                return x-y;
        }
};
Abstract * operatorFactory(COMPOSITION_OPERATOR id) {
  switch(id) {
    case ADDITION:
      return new CompositionOperator<ADDITION>;
    case SUBTRACTION:
      return new CompositionOperator<SUBTRACTION>;
    default:
      throw std::out_of_range("id sconosciuto");
}
Ciao,
Nicola

 devono essere gia' tutte istanziate. Quello che puo' fare e' scegliere
Questo si può chiaramente sempre fare, ma temo che Giuseppe chiedesse 
qualcosa di diverso (e impossibile).
Comunque, a pensarci bene, non vedo a cosa serva poter "ricostruire" 
qualsiasi istanza quando il codice che si è scritto ne può 
conoscere/usare soltanto un numero finito. E' chiaro che basta sapere 
costruire quelle usate. Quindi il problema di Giuseppe non mi è per 
niente chiaro.
Kiuhnm

 lentissimo.
Con Python si potrebbe fare. Ma presumo con il grosso dei linguaggi
interpretati.
-- 
  Giuseppe "Cowo" Corbelli \/\ My software: 
http://cowo.yoda2000.net
    **ANTI-SPAM** -- **Substitute IHATESPAM with cowo to reply by mail**
  -<! Computers are not intelligent.  They only think they are. !>-

On Fri, 24 Mar 2006 15:37:47 +0100, Kiuhnm 
was talkin'about:
#define IMHO

Naturalmente. Però il problema me lo sono posto perché sono sicuro di avere
già definito tutte le specializzazioni possibili a compile time, quindi che
il template é in realtà già stato istanziato per fare quello che mi serve.
-- 
  Giuseppe "Cowo" Corbelli \/\ My software: 
http://cowo.yoda2000.net
    **ANTI-SPAM** -- **Substitute IHATESPAM with cowo to reply by mail**


 da quale di queste istanziare un oggetto. Qualcosa come:
...
Thanks.
Certo la factory é una soluzione praticabile. Speravo di poter evitare
lavoro a runtime, ma a quanto pare ciccia.
-- 
  Giuseppe "Cowo" Corbelli \/\ My software: 
http://cowo.yoda2000.net
    **ANTI-SPAM** -- **Substitute IHATESPAM with cowo to reply by mail**


On Fri, 24 Mar 2006 16:57:10 +0100, Kiuhnm 
was talkin'about:
#define IMHO

Ho un certo insieme di operazioni che posso compiere su un set di dati.
Un utente può voler comporre queste operazioni per creare degli operatori
composti. Gli operatori composti devono essere mantenuti fra diversi run del
programma, per cui devo trovare il modo di serializzarli.
Gli operatori di base sono identificati da un nome; attraverso questo nome
sono in grado da un registro interno di ottenere il function object degli
operatori di base.
A questo punto mi serve ottenere il function object associato all'operazione
di composizione.
Forse é la volta buona che mi decido a vedere come usare boost::spirit.
-- 
  Giuseppe "Cowo" Corbelli \/\ My software: 
http://cowo.yoda2000.net
    **ANTI-SPAM** -- **Substitute IHATESPAM with cowo to reply by mail**


 Forse é la volta buona che mi decido a vedere come usare boost::spirit.
Non risolveresti niente. Spirit li crea tramite i template quindi in 
compile-time.
Kiuhnm

 il template é in realtà già stato istanziato per fare quello che mi serve.
Questo sostanzialmente riduce il tuo problema a qualcosa come:
AbstractOperator * create(int i) {
  switch(i) {
    case 1:
      return new ConcreteOperator<1>;
    case 2:
      return new ConcreteOperator<2>;
}
Francamente non saprei torvare un modo piu' furbo/elegante di ottenere
la stessa cosa; puoi provare a postare sulla mailing list degli utenti
Boost.
Ciao,
Nicola

 Francamente non saprei torvare un modo piu' furbo/elegante...
Non so se questo sia pià furbo, o più elegante, ma se le instanziazioni
possibili di ConcreteOperator sono centinaia, anziché avere un grosso
switch, si potrebbe ricorrere a un po' di metaprogrammazione ricorsiva... 
 template< int I > AbstractOperator* create( int i );
 template<> AbstractOperator* create<0>( int i ) 
       { return new  ConcreteOperator<0>(); }
 template< int I >
 AbstractOperator* create(int i )
 {      switch(i)
        {
                case I: return new ConcreteOperator<I>;
                default: return create< I-1 >(i);       
        }
 };
 AbstractOperator* op = create< MAX_VAL_OF_I >( 10 );
 
Max

  AbstractOperator* op = create< MAX_VAL_OF_I >( 10 );
Bello, ma produrrà codice abbastanza veloce?
Il compilatore è abbastanza "intelligente" da non produrre una serie 
interminabile di if-then-else? I template li sto studiando a fondo 
proprio in questi giorni quindi non saprei.
Kiuhnm

 interminabile di if-then-else? 
Se vogliamo il massimo dell'efficienza, possiamo  usare la stessa tecnica
non per instanziare l'oggetto ma per costruire una tabella di puntatori a
funzioni factory.
 
  typedef AbstractOperator* ( *factory_func_t )();
  factory_func_t  factory_table[ MAX_VAL_OF_I + 1 ];
 
  template< int I >
  AbstractOperator* create() { return new ConcreteOperator<I>(); }
 
  template< int I > void setup_table();
  template<> void setup_table<0>()  { factory_table[0] = &create<0>; }
  template< int I > void setup_table()
   {      
        factory_table[ I ] = &create<I>;
        setup_table< I-1 >();       
   };
 
   AbstractOperator* create(int i ) { return (factory_table[i])(); }
 
 
  int main()
  {
        setup_table<MAX_VAL_OF_I>();
        AbstractOperator* op = create( 99 );
  }
 
 
Max

 proprio in questi giorni quindi non saprei.
Non sono un mago dell'assembler, ma direi che per la versione
modificata qui sotto VC++2005 genera dal main la singola chiamata
diretta a
create< 5 >( 5 );
che nel suo corpo chiama l'operatore new e direttamente l'inserimento
di '5' e '\n' in std::cout. Non male. BCB6 addiruttura espande tutto
nel main. Ancora meglio.
Ciao,
Nicola
#include <iostream>
class AbstractOperator
{
};
template < int UI > class ConcreteOperator : public AbstractOperator
{
public:
	ConcreteOperator() { std::cout << "ConcreteOperator<" << UI << '\n'; }
};
template< int I > AbstractOperator* create( int i );
template<> inline AbstractOperator* create<0>( int i )
       { return new  ConcreteOperator<0>(); }
template< int I >
inline AbstractOperator* create(int i )
{
	return i == I ? new ConcreteOperator<I> : create< I-1 >(i);
};
int main()
{
	AbstractOperator* op = create< 11 >( 5 ); 
}

Nicola Musatti wrote:
[...]
Se invece cambio il main cosi':
int main()
 {
  int n = 0;
  std::cin >> n;
  AbstractOperator* op = create< 11 >( n );
}
Borland continua a generarmi tutto inline, ma VC++ genera tutte le
chiamate...
Ciao,
Nicola

 compile-time.
Probabilmente dovrò cambiare la struttura e buttare l'idea che ho
attualmente applicato. Però ci guadagno in flessibilità.
-- 
  Giuseppe "Cowo" Corbelli \/\ My software: 
http://cowo.yoda2000.net
    **ANTI-SPAM** -- **Substitute IHATESPAM with cowo to reply by mail** 
       -<! Riding from wastelands Black were their skies
              My armour's still bleeding Ripped by fire and ice !>-
                    Lost Horizon - The Kingdom Of My Will

 }
Tieni però presente che in generale non conosciamo quali oggetti 
costruiremo in run-time quindi non potremo passare una costante a create.
Kiuhnm

 chiamate...
Si che VC++ non esegue l'inlining di funzioni ricorsive. E' un peccato, 
direi.
Kiuhnm

   }
Così è praticamente perfetto. Sarebbe interessante vedere se è possibile 
  creare la tavola direttamente in compile-time :)
Kiuhnm

  creare la tavola direttamente in compile-time :)
E' possibile generare (nel vero senso della parola) codice con 
boost::preprocessor.
Kiuhnm

 lentissimo.
Lentissimo è un concetto relativo.
Comunque è lentissimo non perché "permette di fare cose del genere" ma 
perché è un linguaggio interamente interpretato (non è nemmeno compilato 
a pseudocodice, ma proprio interpretato).
Stanno lavorando ad un nuovo modello di esecuzione che avrà un runtime 
misto di pseudocodice con jit compiler mantenendo la capacità 
interpretativa. Dovrebbe renderlo molto più prestante.


La factory classicamente si implementa con una std::map.
Ma non sono certo che gli serva complicarsi la vita con dei template.
Basta una semplice gerarchia di classi.
Il pattern è "Strategy".
L'instanziazione è fatta con "Factory Method".
L'implementazione della factory è un lookup di una std::map che mappa 
l'id (un intero? Una stringa?) usato per la persistenza (meglio ancora 
lo stesso usato per la specifica iniziale) ad una istanza concreta della 
Strategy.
Il popolamento della std::map deve essere fatto a livello di 
inizializzazione. Vi sono varie possibilità fra cui.
1) creare una funzione "init" statica della classe astratta della 
strategy (vantaggio: +controllo, svantaggi: dipendenza da tutte le 
classi concrete della strategy e bisogna ricordarsi di inizializzare).
2) le classi concrete della strategy sono "singleton" che si auto 
istanziano staticamente (con un membro static o con una static a livello 
di file di implementazione). Se l'identificativo è un membro della 
classe astratta, il costruttore protetto della stessa può contenere 
tutta la logica di 'registrazione' nella std::map.
3) se gli algoritmi fossero più complessi ("template Method"?) e 
soprattutto se non sono stateless, allora le classi concrete della
strategy non possono essere singleton. Ma il meccanismo di cui al punto 
2 vale ancora se la factory usa "Prototype".
esempio (nota: scrivo a newsreader, occhio agli errori):
// esiste tipo id_t come identificativo
// quanto segue è accessibile dal programma
class Strategy {
    static std::map<id_t, Strategy*> registry;
    Strategy(); //not implemented
    Strategy(Strategy&); //not implemented
protected:
    virtual ~Strategy()=0;
    Strategy( const id_t& myid )
      :id(myid)
    {
	// maybe you want to make a check
         // and throw if id already registered
         registry[myid] = this;
    }
public:
    const id_t id;
    virtual .... =0; // il tuo operatore, come metodo astratto
    static Strategy* GetIt( const id_t& id ) { return registry[id]; }
};
// nota: le classi concrete non serve che siano visibili dal
// programma. Possono stare in un unico file, oppure in vari
// file
struct ConcreteStrategyAAA: public Strategy {
   .....// l'opeartore concreto
   ConcreteStrategyAAA(const id_t &myid):
      Strategy( myid )
   {
   }
};
// this needed to populate the registry
// specify some UNIQUE id as parameter
// it must be always the same on each and every run
static ConcreteStrategyAAA AAA( ... );
Nota finale.
L'inizializzazione/deinizializzazione delle statiche è garantita 
avvenire correttamente.
Ma non si devono usare questi oggetti se non dopo l'ingresso in main (e 
mai dopo l'uscita da main).

 mai dopo l'uscita da main).
Questa mi pare una contraddizione in termini: le variabili statiche non
possono essere usate fuori dal main proprio perche' la costruzione
*non* e' garantita avvenire correttamente (o meglio nell'ordine delle
eventuali interdipendenze con altre variabili statiche), a meno che,
come suggerisci, non si trovino tutte nella stessa unita' di
traduzione. Un altro problema di cui si sente periodicamente e' che
questo approccio non si presta ad essere utilizzato in librerie
statiche.
Ciao,
Nicola

 traduzione.
Mi sono spiegato male.
Anzi peggio.
Volevo dire proprio che il codice così come scritto NON garantisce la 
corretta inizializzazione se le classi suddette devono, per qualche 
motivo, essere usate fuori dal main. Mentre tutto va bene se le usi 
"normalmente".

 a pseudocodice, ma proprio interpretato).
Non ho mai affermato il contrario.
Kiuhnm

 sequenza di if. Ma a questo punto perdi di efficienza e scalabilità.
Senza dubbio.
Ciao,
Nicola