OVH Cloud OVH Cloud

Problème de template

18 réponses
Avatar
Michaël Delva
Salut à tous,

J'ai créé une fonction qui me permet de remplir un vecteur par les
différents éléments d'une chaine std::string:

void __fastcall Tokenize(const std::string & chaine, std::vector<int> &
tokens, const std::string & delimiters)
{
tokens.clear();

std::string::size_type start=0, end;

while((start = chaine.find_first_not_of(delimiters, start)) !=
std::string::npos)
{
end = chaine.find_first_of(delimiters, start);
tokens.push_back(StrToInt(chaine.substr(start, end-start).c_str
()));
start=end;
}
}


void __fastcall Tokenize(const std::string & chaine, std::vector
<std::string> & tokens, const std::string & delimiters)
{
tokens.clear();

std::string::size_type start=0, end;

while((start = chaine.find_first_not_of(delimiters, start)) !=
std::string::npos)
{
end = chaine.find_first_of(delimiters, start);
tokens.push_back(chaine.substr(start, end-start).c_str());
start=end;
}
}

Je l'ai surchargé pour pouvoir remplir un vector<int> ou bien un vector
<std::string>

Mais là je voudrais la même fonction pour un vector<unsigned int>, et je me
dis que plutôt que de refaire une troisième fonction identique, je pourrais
utiliser les templates.

Seulement je sais pas faire :(

Comment je peux faire ça?

Merci d'avance

10 réponses

1 2
Avatar
Fabien LE LEZ
On 10 Aug 2004 17:13:18 GMT, "Michaël Delva"
:

Mais là je voudrais la même fonction pour un vector<unsigned int>, et je me
dis que plutôt que de refaire une troisième fonction identique, je pourrais
utiliser les templates.


Yep. Je dirais même plus : tu as déjà deux fonctions qui font la même
chose, il y a donc un problème.

template <class Element> void Tokenize
(const std::string & chaine,
std::vector<Element> & tokens,
const std::string & delimiters)
{
tokens.clear();

std::string::size_type start= 0;

while((start = chaine.find_first_not_of(delimiters, start)) !=
std::string::npos)
{
std::string::size_type
end= chaine.find_first_of(delimiters, start);
Element e;
Convertir (e, chaine.substr(start, end-start));
tokens.push_back (e);
start=end;
/* Note : je n'ai pas changé la boucle car je ne sais pas ce que tu
veux faire exactement */
}
}

void Convertir (int& out, std::string const& s);
void Convertir (unsigned int& out, std::string const& s);

void Convertir (std::string const& out, std::string const& s)
{
out= s;
}




--
;-)

Avatar
Jean-Marc Bourguet
Fabien LE LEZ writes:

J'ai bien aime ton choix pour l'autre message ou tu donnes la meme
reponse...

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Arnaud Debaene
Michaël Delva wrote:
Salut à tous,

J'ai créé une fonction qui me permet de remplir un vecteur par les
différents éléments d'une chaine std::string:


<snip>

Je l'ai surchargé pour pouvoir remplir un vector<int> ou bien un
vector <std::string>

Mais là je voudrais la même fonction pour un vector<unsigned int>, et
je me dis que plutôt que de refaire une troisième fonction identique,
je pourrais utiliser les templates.

Seulement je sais pas faire :(

Comment je peux faire ça?


Commences par analyser ton code existant : tes 2 fonctions sont strictement
identiques à une ligne près :

tokens.push_back(StrToInt(chaine.substr(start, end-start).c_str ()));
//version int

tokens.push_back(chaine.substr(start, end-start).c_str()); //version
string


A partir de là, on voit que, pour écrire une version générique de ta
fonction, il va nous falloir un paramètre supplémentaire : un
objet/fonction/truc d'insertion qui prend en entrée un std::string et
recrache en sortie la conversion de cette chaîne en type adapté à ton
conteneur cible.

Appelons cette "chose" StringConverter, et prenons comme convention que
c'est un objet, et que c'est son opérateur () qui doit être appelé pour
faire cette conversion. La version générique de StringConverter s'écrit
ainsi:

template <class T> struct StringConverter
{
inline T operator() (const std::string& s)
{
return s;
}
};
Cette version générique fonctionnera pour n'importe quel type "implicitement
constructible" depuis une std::string, y compris std::string elle-même!

Maintenant, écrivons une spécialisation de notre petite classe de conversion
pour les int :
template<> struct StringConverter<int>
{
inline int operator() (const std::string& s)
{
return StrToInt(s);
}
};

Tu peux écrire d'autres spécialisations de StringConverter pour tous les
types dont tu peux avoir besoin : unsigned int ou n'importe quoi d'autre.

Maintenant, il ne nous reste plus qu'à écrire une version template de ta
fonction Tokenize. Il y a un seul paramètre template : le type du vecteur de
sortie.
template <class OutputType> void __fastcall Tokenize(const std::string &
chaine, std::vector<OutputType> &tokens, const std::string & delimiters)
{
StringConverter<OutputType> converter; //objet qui va être utilisé pour
obtenir la conversion de chaque sous-chaîne en OutputType.
tokens.clear();

std::string::size_type start=0, end;

while((start = chaine.find_first_not_of(delimiters, start)) ! std::string::npos)
{
end = chaine.find_first_of(delimiters, start);
tokens.push_back(converter(chaine.substr(start, end-start)));
start=end;
}
}

Et voilà! Pour l'appel de cete fonction, tu n'as même pas besoin de
spécifier le paramètre template, il est déduit du type du paramètre "tokens"
:
std::vector<int> mes_tokens;
Tokenize(ma_chaine, mes_tokens, mes_delimiteurs); //appelée avec un
vector<int>, donc OutputType==int.

Notes bien que c'est une solution parmi d'autres, et pas forcément la
meilleure, vu qu'il faut définir une spécialisation de TypeConverter pour
tous les types que tu veux utiliser (il est certainement possible de faire
mieux avec des typetraits), mais c'est une 1ère approche.

<standard disclaimer> Code écrit à la va-vite et non testé, fourni sans
aucune garantie :-) </standard disclaimer>

Arnaud

Avatar
Fabien LE LEZ
On 11 Aug 2004 09:49:01 +0200, Jean-Marc Bourguet :

J'ai bien aime ton choix pour l'autre message ou tu donnes la meme
reponse...


Autre message ? Je sais que j'ai écrit deux fois le même message, mais
c'est parce que le premier a disparu de ma boîte d'envoi (qui garde
pourtant les messages jusqu'à ce que je les efface à la main), et
n'apparaît pas sur mon serveur (news.free.fr). Je ne sais pas du tout
ce qui s'est passé.


--
;-)

Avatar
Jean-Marc Bourguet
Fabien LE LEZ writes:

On 11 Aug 2004 09:49:01 +0200, Jean-Marc Bourguet :

J'ai bien aime ton choix pour l'autre message ou tu donnes la meme
reponse...


Autre message ? Je sais que j'ai écrit deux fois le même message, mais
c'est parce que le premier a disparu de ma boîte d'envoi (qui garde
pourtant les messages jusqu'à ce que je les efface à la main), et
n'apparaît pas sur mon serveur (news.free.fr). Je ne sais pas du tout
ce qui s'est passé.


Je l'ai lu sur news.free.fr, en reponse a un de mes messages ou il est
question de marais et de crocodiles :-)

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Fabien LE LEZ
On 11 Aug 2004 11:47:49 +0200, Jean-Marc Bourguet :

Je l'ai lu sur news.free.fr, en reponse a un de mes messages ou il est
question de marais et de crocodiles :-)


Ah, OK !
Je n'avais pas compris que tu parlais de l'autre thread.
Quand tu dis "l'autre message" alors que j'en ai envoyé 30, c'est
difficile de suivre ;-)

Euh... D'ailleurs, de quoi tu parles exactement ? Tu as écrit "Fabien
LE LEZ writes:" mais avec aucune citation.
Message parti trop vite ?




--
;-)

Avatar
Michaël Delva
Merci à vous deux pour vos réponses et éclaircissements...

Je commence à me rendre compte de la puissance des templates!

Faudra que je m'y interesse sérieusement bientôt!

Encore merci...
Avatar
Michaël Delva
std::vector<int> mes_tokens;
Tokenize(ma_chaine, mes_tokens, mes_delimiteurs); //appelée avec un
vector<int>, donc OutputType==int.

Notes bien que c'est une solution parmi d'autres, et pas forcément la
meilleure, vu qu'il faut définir une spécialisation de TypeConverter
pour tous les types que tu veux utiliser (il est certainement possible
de faire mieux avec des typetraits), mais c'est une 1ère approche.

<standard disclaimer> Code écrit à la va-vite et non testé, fourni
sans aucune garantie :-) </standard disclaimer>

Arnaud



Ton code marche parfaitement bien ;)

Et tu parles de typetraits, que je ne connais pas... ça consiste en quoi?

Avatar
Arnaud Debaene
Michaël Delva wrote:

Ton code marche parfaitement bien ;)
Ouf! ;-)


Et tu parles de typetraits, que je ne connais pas... ça consiste en
quoi?


Googlise un peu sur le sujet. L'idée serait par exemple d'avoir une seule
version de StringConverter pour tous les types numériques intégraux (int,
short, long, unsigned ou pas), et de sélectionner cette spécialisation pour
tous ces types en se basant sur un "trait" (ou "tag"si tu préfère) qui
indique que ces types sont efectivement des numériques intégraux.

Arnaud

Avatar
Fabien LE LEZ
On 11 Aug 2004 11:13:30 GMT, "Michaël Delva"
:

Et tu parles de typetraits, que je ne connais pas... ça consiste en quoi?


J'imagine qu'il s'agit des "traits" (je ne sais pas s'il y a un
équivalent en français). Il s'agit de classes dont tous les membres
sont statiques, et qui donne des informations générales (généralement,
sur un type).

Le plus classique est std::numeric_limits<>.

Autre exemple :

class TerminaisonZero
{
static char const caractere_fin= '';
};

class TerminaisonFinLigne
{
static char const caractere_fin= 'n';
};

template <class Trait> class BasicString
{
public:
size_t Longueur()
{
int reponse= 0;
while (data[reponse] != Trait::caractere_fin) ++reponse;
return reponse;
}
};

typedef BasicString<TerminaisonZero> ChaineClassique;
typedef BasicString<TerminaisonFinLigne> ChaineLigne;

"TerminaisonZero" et "TerminaisonFinLigne" sont des traits.


--
;-)

1 2