Newsgroup: it.comp.lang.c++
Titolo: i template mi fanno impazzire

salve a tutti,
scusate la domanda forse da niubbo, ma non sono molto avvezzo a
giocherellare con i template, e oggi ho perso un po' di orette su una
cosa che mi fa impazzire... eterna riconoscenza a chi mi da' una
dritta :)
Ora, io ho un template di questo tipo...
// pippo.h -------------------------------------
template<class T, int i> class CHistory{
        unsigned int maxlen;
        deque<T> tmp;
public:
        deque<T> hist;
        CHistory() : maxlen(i) {}
        void push(T val){
             hist.push_front(val);
             while(hist.size() > maxlen)
                    hist.pop_back();
        }
		// ecc. ecc. ecc.
};
//---------------------------------------------
e funziona tutto a meraviglia. Se pero' provo a portare le
definizioni delle funzioni fuori dalla classe, in un file .cpp a
parte...
// pippo.h -----------------------------------
template<class T, int i> class CHistory{
        unsigned int maxlen;
        deque<T> tmp;
public:
        deque<T> hist;
        CHistory() : maxlen(i) {}
        void push(T val);
		// ecc. ecc. ecc.
};
----------------------------------------------
// pippo.cpp ---------------------------------
template<class T, int i> void CHistory<T,i>::push(T val)
{
        hist.push_front(val);
        while(hist.size() > maxlen)
                hist.pop_back();
}
// ecc. ecc. ecc.
----------------------------------------------
il compilatore (in questo caso un microsoft visual studio .net 2003)
si incazza come una jena, lamentandosi di cose del tipo...
GenObject.obj : error LNK2019: unresolved external symbol "public:
void __thiscall CHistory<float,25>::push(float)"
(?push@?$CHistory@M$0BJ@@@QAEXM@Z) referenced in function "private:
void __thiscall CGenObject::GetKalmanPrediction(void)"
(?GetKalmanPrediction@CGenObject@@AAEXXZ)
Insomma, per qualche motivo a me oscuro non riesce a trovare le
funzioni definite nel file .cpp... qualche idea del perche' succeda?
Grazie in anticipo
-- 
addio, e grazie per tutto il pesce

 dritta :)
Tu hai bisogno di leggere la FAQ. :-) Arriva nel prossimo messaggio...
EMG
--
EMAG Software Homepage:     
http://www.emagsoftware.it
The EMG's ZX-Spectrum Page: 
http://www.emagsoftware.it/spectrum
The Best of Spectrum Games: 
http://www.emagsoftware.it/tbosg

 giocherellare con i template,
A proposito: la domanda che ti interessa è la numero 10.


 ha
scritto:

Ciao,
il problema può essere (parzialmente, ma in modo abbastanza soddisfacente)
risolto con un paio di 'trucchi'. Qui di seguito ti riporto parte di un mio
vecchio esercizio che implementava un database di oggetti di tipo T con i
templates. Lascia perdere la validità del codice come autentico database e
concentrati solo sulle soluzioni che ti permettono di separare la
dichiarazione della classe template dalla definizione della stessa su due
file "database.h" e "database.cpp".
// file database.h
#ifndef _DATABASE_H_
#define _DATABASE_H_
#include <iostream>
#include <fstream>
#include <string>
using std::ostream;
using std::fstream;
using std::string;
template <typename T>
class DataBase;
template <typename T>
ostream& operator<<( ostream &out, DataBase<T> &db ); // friend template
functions must be forward declared
template <typename T>
class DataBase
{
public:
      DataBase();
private:
      fstream dataFile;
      string fName; // when passing string fname to open() remember to call
fName.c_str()
      void run();
      void add( T & );
      bool find( const T & );
      void modify( const T & );
      void remove( T & );
      ostream& print( ostream & );
      friend ostream& operator<< <> ( ostream &out, DataBase<T> &db ); // Note the
unusual symbol <>
};
#endif
 
// file database.cpp
#include "person.h"
#include "database.h"
using std::ios;
template class DataBase<Person>; // riga da modificare per te come scrivo
più giù
template <typename T>
DataBase<T>::DataBase()
{
    cin.clear();
    while ( true )
    {
        cout << "\nEnter DataBase complete path: ";
        if ( ( cin >> fName ) || ! cin.eof() )
        {
            if ( cin.good() )
                this->run();
            else
            {
                cin.clear();
                cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
                cout << "\nBad input!";
            }
        }
        else
        {
            cin.clear();
            break;
        }
    }
}
template <typename T>
void DataBase<T>::run()
{
 
    int option;
    T record;
    do
    {
        cout << "1. Add 2. Find 3. Modify 4. Exit\n";
        if ( !( cin >> option ) || ( option < 1 ) || ( option > 4 ) )
        {
            cout << "Bad option!\n\n";
            cin.clear();
            cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
            continue;
        }
        switch ( option )
        {
        case 1:
            cin >> record; // overloaded ">>"
            add( record );
            break;
            // salto un bel pezzo di codice, comunque puoi compilare senza
     }
    }
    while ( option != 4 );
    cout << "End of DataBase use\n\n";
    cout.flush();
}
template <typename T>
void DataBase<T>::add( T &record )
{
    dataFile.open( fName.c_str(), ios::out | ios::app );
    dataFile.seekp(0,ios::end);
    record.writeToFile(dataFile);
    dataFile.close();
}
template <typename T>
inline ostream& operator<<( ostream &out, DataBase<T> &db )
{
    return db.print( out );
}
// file usedb.cpp
#include "person.h"
#include "database.h"
int main()
{
      DataBase<Person> dbPerson; // devi modificare anche questa per
DataBase<char> o per qualsiasi altro tipo di oggetto vuoi usare
      return (EXIT_SUCCESS);
}
Il codice che ti ho postato è più che sufficente per farti vedere come
funziona la possibilità di 'splittare' il codice come da premessa. Lo puoi
compilare, se non ho fatto errori con il taglia e incolla, così come è con
l'unica avvertenza di togliere gli "include" di "person.h" e di modificare
la riga che ti ho fatto notare sopra con ad esempio "template class
DataBase<char>;" (nel file database.cpp) e la riga di usedb.cpp in modo
appropriato.
In sintesi guarda come è possibile, se specifichi all'inizio del file
"database.cpp" i vari tipi per cui il template sarà istanziato farlo
funzionare anche se su files separati. Ti ho fatto vedere anche come usare i
friend template functions. Stai attento al fatto che devono essere
forward-declared e soprattutto stai attento al simbolo "<>" all'interno
della classe.
Se non ti è chiaro qualcosa o se hai bisogno anche di "person.h" e
"person.cpp" per fare funzionare il tutto fammi saapere (comunque se fai le
modifiche che ti ho detto puoi usare oggetti di qualsiasi tua classe oppure
tipi predefiniti come char o int per provare).
Come ultima cosa devo ringraziare il Gruppo comp.lang.c++ che a suo tempo mi
ha aiutato a comprendere la soluzione a questo problema e oggi mi da la
possibilità di fare altrettanto su questo Gruppo.
Saluti,
fabiomdf
 
--------------------------------
Inviato via 
http://arianna.libero.it/usenet/

 fabiomdf
Mi è venuto il dubbio che dal mio post si possa pensare che in questo modo
il template è utilizzabile solo per un tipo alla volta. No, non è così. Si
possono elencare sul database.cpp tutti i tipi per i quali si presuppone che
il template sarà isanziato con le seguenti semplici istruzioni una dietro
l'altra:
template class DataBase<Person>;
template class DataBase<Shape>;
template class DataBase<char *>
...
Quindi quando parlavo di soluzione parziale intendevo questo, si devono
immaginare a priori i tipi per i quali la classe template sarà instanziata,
anche se questi sono molti (io ho provato con due diversi tipi e funziona,
quindi immagino che se vale per due vale anche per tre o più).
Buon lavoro,


 parte...
Semplicemente non puoi... almeno per ora.
http://www.comeaucomputing.com/4.0/docs/userman/export.html
--
Fabio.


Scusa, ma ti sei dato pena di leggere gli altri posts di questo thread? Se
non sei d'accordo su quanto ho scritto, dimmi dove sbaglio. Tieni anche
conto che non ho parlato di teoria ma di applicazioni che realmente
compilano, almeno con gcc-3.4.x e funzionano.
In pratica, e lasciamo perdere per ora il problema delle "friend template
functions" di cui puoi leggere esempio funzionante nel mio post, puoi
assolutamente spezzare in due files (un .h più un .cpp) la dichiarazione di
una classe template e la definizione della stessa semplicemente scrivendo in
testa al codice del file .cpp i tipi previsti per cui il template sarà
istanziato.
Una semplice dichiarazione dei tipi per cui il template sarà istanziato si
scrive:
template class Classe<Tipo>;
dove Classe è la tua classe template e Tipo può essere qualsiasi tipo
built-in oppure tipi user-defined in classi esterne da includere come file
.h all'inizio del .cpp (intendo il .cpp dove la classe template è definita).
Francamente non capisco che tipo di aiuto dai a chi ha il problema da
risolvere e ha postato la domanda di chiarimento se ti limiti a dire che
quanto è stato scritto in altri replies sono solo cazzate senza fornire una
contro-argomentazione in proposito.
E comunque ignorare gli altri interventi non mi sembra neppure molto
educato...
Secondo te il Richiedente dovrebbe arrendersi nel trovare una soluzione solo
perchè scrivi che non c'è modo di risolvere il problema senza neppure
spiegare il motivo?
Il codice che ho scritto lo puoi compilare, fatte le poche modifiche che ho
descritto, e se hai qualche errore chiedi pure.
Saluti,


 template sarà istanziato.
[...]
Il punto e' proprio questo:
Quando definisci una classe template in genere non vuoi/puoi prevedere 
i tipi per cui verra' istanziato. I template aiutano nel riuso del codice
e un template che scrivi oggi per un certo tipo potrai usarlo domani per 
un altro. Quindi, almeno per ora, l'unica e' avere le implementazioni 
dei metodi in un file .h (di solito si spezza la classe in classe.h e
classe-inl.h, cosi, chi ha bisogno solo delle dichiarazioni include
"classe.h" e solo chi effettivamente usa la classe e invoca i vari metodi
include "classe-inl.h").
Saluti,
Un'altro Fabio


Ciao,
Ulp!.. Ma quanti Fabio?
Corretta osservazione almeno dal punto di vista teorico... Non vogliamo
decidere a priori quali tipi saranno gestiti dal nostro template. In altro
mio post (stesso thread) ho scritto che così risolviamo il problema
"...almeno parzialmente, seppur in modo soddisfacente...".
Si tratta pur sempre di una soluzione che lascia molti scontenti, però ci
sono dei vantaggi indiscutibili (credo).
Prima di tutto a volte vogliamo che la nostra definizione sia fornita solo
in codice compilato, e quindi far vedere la stessa su un altro file .h non è
molto spesso appropriato.
In secondo luogo non dovrebbe essere un grosso problema, almeno su codice
non troppo generico ma destinato a particolari utilizzi con tipi più o meno
previsti, anticipare anche 10,20 o più tipi che possiamo spesso immaginare.
In ogno caso stiamo sempre riutilizzando il codice della classe template, se
non per tutti i tipi presenti e futuri, almeno per tutti quelli che
riusciamo a prevedere. Quindi sempre di riutilizzo (limitato) del codice si
tratta.


 previsti, anticipare anche 10,20 o più tipi che possiamo spesso immaginare.
Tanto più che, sempre se ho capito bene il tuo sistema, se ce ne siamo
dimenticati uno veniamo avvisati in compilazione, o sbaglio?



ha scritto:

Si, corretto. Siamo avvisati dal linker in fase di post-compilazione.
Se non abbiamo voglia di modificare il nostro .cpp con la precedente
definizione dei templates possiamo anche includerlo in un nuovo header dove
aggiungeremo la dichiarazione di istanza:
// file templates_inst.h
#include "templates.cpp" // si hai letto bene, includiamo un *.cpp
template class handle<double>;
Quindi nel main.cpp includeremo "templates_inst.h" al posto di templates.h
(vedi esempio in FAQ).



Scusate ho fatto un errore: nella frase sopra va modificato "... al posto di
templates.h ..." con "... al posto di handle.h... ".
