Problème de fonction template et non template [Résolu]

Messages postés
642
Date d'inscription
mercredi 10 septembre 2008
Statut
Membre
Dernière intervention
9 février 2017
- - Dernière réponse : cptpingu
Messages postés
3835
Date d'inscription
dimanche 12 décembre 2004
Statut
Modérateur
Dernière intervention
10 juin 2019
- 15 sept. 2014 à 13:34
Bonjour,

Alors voici mon problème. Je vais essayer d'être aussi clair que possible. Mais après une demi journée de recherche je me tourne sur le forum.
Alors voilà mon objectif est de lire un fichier texte de la forme :
TSTART=01
TSTOP=2726
etc....

Dans une classe InputParameter je surcharge l'opérateur << comme ceci :
fichier.h :

class InputParameter {
public: int &getIntervalTime();
private : double m_tStart;
};
std::ifstream& operator>>(std::ifstream &in, InputParameter & param);

fichier.cpp :

std::ifstream& operator>>(std::ifstream &in, InputParameter ¶m)
{
ini init(in);
init.Set("TSTART", param.getIntervalTime());
return in;
}

Ensuite dans une classe ini je défini mes fonctions Set() pour mon opérateur << comme ceci :
fichier.h :

class ini {
public :
template <typename T> void Set(std::string const &variable, T &value);
void Set(std::string const &variable, bool &value);
void Set(std::string const &variable, std::string &value);

private : "some private method"
};

// For booleans: reads the variable name and sets its value from file
inline void ini::Set(std::string const &variable, bool &value)
{
"managed some boolean"
}

// For strings: reads the variable name and sets its value from file
inline void ini::Set(std::string const &variable, std::string &value)
{
"managed some string"
}

template <typename T>
void ini::Set(std::string const &variable, T &value)
{
managed some natural type eg : int, double, float etc...
}

Comme vous pouvez le voir ci dessus, j'ai donc 3 méthodes Set() dont 2 inline et une template.
Dans le fichier ini.cpp je gère que mes méthodes privées.

Enfin dans le main.cpp j'ai ceci :

main.cpp :

#include <iostream>
#include "ModelParameters/inputparameter.h"

int main()
{
try
{
InputParameter param;
std::ifstream in("someFile.txt");
in >> param;
}
catch(std::exception &e)
{
std::cout << "error occured !" << e.what() << std::endl;
}

}

Ce code compile mais ne fonctionne pas comme je le souhaite.
En fait quand je regarde l'auto complétion de code dans Qt, il ne me propose pas ma fonction template, un peu comme si elle était inexistante.
En mode debug c'est le cas aussi, au lieu d'utiliser ma fonction template :

Code (comme je traite un double) :

template <typename T>
void ini::Set(std::string const &variable, T &value)

il utilise la méthode :
Code :

 
inline void ini::Set(std::string const &variable, std::string &value)

Donc c'est pas bon !
Ce qui est encore plus incompréhensible pour moi, c'est que si je commente mes 2 fonctions "normales" (non template) alors la mon programme se comporte correctement et fais ce que je veux ????

Donc avez vous une piste d'explication pour moi car la je sèche lamentablement.
Merci d'avance pour ceux qui pourront éclairer mon chemin obscure

++ dodo

Afficher la suite 

10 réponses

Meilleure réponse
Messages postés
3835
Date d'inscription
dimanche 12 décembre 2004
Statut
Modérateur
Dernière intervention
10 juin 2019
84
1
Merci
J'ai copié ton exemple, reproduit ton fichier de data, et lancé le programme (en ajoutant 3 std::cout pour savoir dans laquelle des 3 méthodes j'entrait).
J'entre bien dans la méthode avec template.
Ton erreur est forcément ailleurs, car ces routines ne sont pas ambigües, et ça fonctionne très bien.

__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature

Dire « Merci » 1

Quelques mots de remerciements seront grandement appréciés. Ajouter un commentaire

Codes Sources 135 internautes nous ont dit merci ce mois-ci

Commenter la réponse de cptpingu
Messages postés
3835
Date d'inscription
dimanche 12 décembre 2004
Statut
Modérateur
Dernière intervention
10 juin 2019
84
0
Merci
Bonjour.

J'ai essayé, à partir de ton code, de reproduire un exemple minimaliste. Je n'ai pas reproduit ton problème. Peux-tu poster un code compilable complet mais minimaliste, qui reproduit ton souci ?

Voici mon essaie, qui fonctionne parfaitement chez moi:
#include <iostream>
#include <fstream>

class ini // Une class prend généralement une majuscule
{
public:
  ini(std::ifstream&) // On colle le "&" au type en C++ (contrairement au C).
  {
  }

  template <typename T>
  void Set(std::string const&, T&) // Methode sans majuscule, réservé aux classes
  {
    std::cout << "managed some natural" << std::endl;
  }
  void Set(std::string const&, bool&)
  {
    std::cout << "managed some boolean" << std::endl;
  }
  void Set(std::string const&, std::string&)
  {
    std::cout << "managed some string" << std::endl;
  }
};

class InputParameter
{
public:
  int& getIntervalTime() { return m_tStart; } // Lève un warning si m_tStart est un double
private:
  int m_tStart;
};

std::ifstream& operator>>(std::ifstream& in, InputParameter& param)
{
  ini init(in);
  init.Set("TSTART", param.getIntervalTime()); // Design dangereux, on utilise un "setIntervalTime" plutôt que d'affecter le résultat d'un get...
  return in;
}

int main()
{
  try
  {
    InputParameter param;
    std::ifstream in("someFile.txt");
    in >> param;
  }
  catch(std::exception& e)
  {
    std::cout << "error occured !" << e.what() << std::endl;
  }

  return 0;
}


__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
Commenter la réponse de cptpingu
Messages postés
642
Date d'inscription
mercredi 10 septembre 2008
Statut
Membre
Dernière intervention
9 février 2017
4
0
Merci
Cptpingu,
Merci d'avoir pris le temps de jeter un oeil et pour tes commentaires.
Oui c'est possible que je poste le code complet mais comment puis je poster un .zip ?
Commenter la réponse de dodo7263
Messages postés
3835
Date d'inscription
dimanche 12 décembre 2004
Statut
Modérateur
Dernière intervention
10 juin 2019
84
0
Merci
Je ne veux pas ton code complet, je veux un exemple minimaliste (compilable) qui reproduit ton souci, car je n'arrive pas à reproduire le problème que tu décris.

__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
Commenter la réponse de cptpingu
Messages postés
642
Date d'inscription
mercredi 10 septembre 2008
Statut
Membre
Dernière intervention
9 février 2017
4
0
Merci
En fait, j'ai quasiment posté mon code déjà. Tu as au total 4 classes que voici :

La classe ini :
ini.h :
#ifndef INI_H
#define INI_H

#include <stdexcept>
#include <fstream>
#include <sstream>
#include <iomanip>

//#include "afgen.h"

// ini class
// Reads variable names and set their values from input file
class ini
{
private:
std::ifstream &in_; // data file
std::ios_base::iostate oldstate_; // data file original settings

ini(ini const &);
ini &operator=(ini const &);
void Trim(std::string &str) const;
std::string const Lowercase(std::string const &str) const;
void Parse(std::string const &str, std::string &lhs,std::string &rhs, bool bString);
void FindValue(std::string const &variable, std::string &value);

public:
explicit ini(std::ifstream &file);
~ini();

template <typename T> void Set(std::string const &variable, T &value);
void Set(std::string const &variable, bool &value);
void Set(std::string const &variable, std::string &value);
//void Set(std::string const &variable, afgen &value);
};

// Constructs object and set the I/O flags
inline ini::ini(std::ifstream &file) : in_(file), oldstate_(file.exceptions())
{
in_.exceptions(std::ios_base::failbit | std::ios_base::badbit | std::ios_base::eofbit);
}

inline ini::~ini()
{
in_.exceptions(oldstate_); // restore the I/O flags
}

// Reads the variable name and sets its value from file


// For booleans: reads the variable name and sets its value from file
inline void ini::Set(std::string const &variable, bool &value)
{
std::string str;
FindValue(variable, str);
std::stringstream ss(Lowercase(str));
if (!(ss >> std::boolalpha >> value))
throw std::runtime_error("Bad value read");
}

// For strings: reads the variable name and sets its value from file
inline void ini::Set(std::string const &variable, std::string &value)
{
std::string str;
while (str.empty())
{
char buf[255];
in_ >> buf;
//in_.getline(buf, 255);
str = buf;
Trim(str);
}

std::string lhs, rhs;
Parse(str, lhs, rhs, true);

if (Lowercase(lhs) != Lowercase(variable))
throw std::runtime_error("Variable name not found");

while (rhs.empty())
{
char buf[255];
in_ >> buf;
//in_.getline(buf, 255);
rhs = buf;
Trim(rhs);
}

value = rhs;
}

template <typename T>
void ini::Set(std::string const &variable, T &value)
{
std::string str;
FindValue(variable, str);
std::stringstream ss(str);
if (!(ss >> value))
throw std::runtime_error("Bad value read");
}


// For afgen class: reads the file name and sets the afgen object
/*inline void ini::Set(std::string const &variable, afgen &value)
{
std::string fname; // filename for tabulated data
Set(variable, fname); // get the file name now
value.Filename(fname); // set the afgen object
value.MemoriseTable(); // read in all tabulated data
}*/
#endif // INI_H

Le fichier ini.cpp :

#include <cstring>
#include <cstdlib>
#include <cctype>
#include "ini.h"

// INTERNAL USE: Removes whitespaces, quotes and equal sign from
// given string (str)
void ini::Trim(std::string &str) const
{
while (str[0] == ' ' || str[0] == '=' || str[0] == '\t'
|| str[0] == '"')
str.erase(0, 1);

int cLen = static_cast<int>(str.length());
while (cLen > 0 && (str[cLen-1] == ' ' || str[cLen-1] == '\t'
|| str[cLen-1] == '"'))
str.erase(--cLen, 1);
}

// INTERNAL USE: Converts and returns a string, all chars in lowercase
std::string const ini::Lowercase(std::string const &str) const
{
std::string locase(str);
for (std::string::iterator pos=locase.begin();
pos!=locase.end(); ++pos)
*pos = static_cast<char>(std::tolower(*pos));
return locase;
};

// INTERNAL USE: Splits a given string (str) into two: left- (lhs)
// and right hand side (rhs). Where to split the two is where
// the equal sign (=) is located in str.
// If bString is set to TRUE, the rhs string can contain spaces.
void ini::Parse(std::string const &str, std::string &lhs,
std::string &rhs, bool bString)
{
lhs.clear(); // ensure all empty
rhs.clear();

char buf[255];
std::strcpy(buf, str.c_str());

char *pToken = std::strtok(buf, " =\t");
int nCnt = 0;
while (pToken && nCnt<2)
{
if (++nCnt == 1)
lhs = pToken;
else
{
rhs = pToken;
Trim(rhs);
if (rhs.empty())
--nCnt;
}

if (!bString)
pToken = std::strtok(0, " =\t");
else
pToken = std::strtok(0, "\n");
}
}

// INTERNAL USE: Returns the variable's value as string
void ini::FindValue(std::string const &variable, std::string &value)
{
std::string str;
in_ >> str;

std::string lhs, rhs;
Parse(str, lhs, rhs, false);

if (Lowercase(lhs) != Lowercase(variable))
throw std::runtime_error("Variable name not found");

while (rhs.empty())
{
in_ >> lhs;
std::string unused;
Parse(lhs, rhs, unused, false);
}

value = rhs;
}

La classe inputParameter :
le fichier inputparameter.h

#ifndef INPUTPARAMETER_H
#define INPUTPARAMETER_H

#include<string>
#include<iostream>

#include"ini.h"

class InputParameter
{
public:
InputParameter();

virtual ~InputParameter();

int &getIntervalTime();
void setIntervalTime(int value);

private:

int m_intervalTime;

};

std::ifstream& operator>>(std::ifstream &in, InputParameter& param);

#endif // INPUTPARAMETER_H

le fichier .cpp :

#include "inputparameter.h"

// Default constructor
InputParameter::InputParameter(){}

// Destructor
InputParameter::~InputParameter(){}

int& InputParameter::getIntervalTime()
{
return m_intervalTime;
}

void InputParameter::setIntervalTime(int value)
{
m_intervalTime = value;
}

std::ifstream& operator>>(std::ifstream &in, InputParameter ¶m)
{
ini init(in);
init.Set("TSTART", param.getIntervalTime());
return in;
}

Enfin mon fichier main.cpp :

#include <iostream>
#include "inputparameter.h"

int main()
{
try
{
InputParameter param;
std::ifstream in("file.txt");
in >> param;
}
catch(std::exception &e)
{
std::cout << "error occured !" << e.what() << std::endl;
}

}

Mon objectif est de lire mon fichier texte puis de récuperer les valeurs de ces variables dans mon objet inputParameter via mes get / set pour les utiliser tout au long de ma simulation.

Mon fichier texte est comme ceci :
TSTART=01
DT=0.27
etc...

++ dodo
Commenter la réponse de dodo7263
Messages postés
642
Date d'inscription
mercredi 10 septembre 2008
Statut
Membre
Dernière intervention
9 février 2017
4
0
Merci
ah ben ça alors....je comprends plus rien. bon ben je vais étudier ça plus en détail...Merci quand même et je te tiens au courant...

++dodo
Commenter la réponse de dodo7263
Messages postés
642
Date d'inscription
mercredi 10 septembre 2008
Statut
Membre
Dernière intervention
9 février 2017
4
0
Merci
Bonjour à tous et à cptpingu,

Bon après un week-end au calme sans toucher au code, j'ai effectivement résolu mon problème. Problème qui je crois n'existait tous simplement pas ;-)
Il semble que je me sois emmêler les pinceaux dans les types de variables que j'essayais de stocker dans mon objet InputParameter. J'ai mis tout ça en ordre et cela fonctionne parfaitement.
Je devais être fatigué sans doute....

Toujours est il que "l'intellisense de Qt" ne me propose pas ma fonction template set() lorsque j'écris mon code c'est peut être ça qui m'a dérouté....

P.S :
1) Merci pour tes remarques aussi que je vais bien sur prendre en compte mais j'avais fait un exemple vite fait.

2) Au fait dans une de tes remarques tu parles de design dangereux. pourquoi cela ?
En effet, mon objectif est de stocker tout mon fichier texte "dans une classe" pour que par la suite je puisse accéder à ces différents paramètres que j'utiliserais dans le cadre de ma simulation.
Disons que si dans le constructeur de ma classe InputParameter, j'initialise tous mes membres privés à 0 comme ceci :
// Default constructor
InputParameter::InputParameter(){m_intervalTime = 0; m_tStart = 0;}

est ce correct pour toi ?

Encore merci pour tout cptpingu !
Commenter la réponse de dodo7263
Messages postés
3835
Date d'inscription
dimanche 12 décembre 2004
Statut
Modérateur
Dernière intervention
10 juin 2019
84
0
Merci
Il semble que je me sois emmêler les pinceaux dans les types de variables que j'essayais de stocker dans mon objet InputParameter. J'ai mis tout ça en ordre et cela fonctionne parfaitement.
Je devais être fatigué sans doute....

C'est aussi à cela que sert un forum, avoir une relecture qui permet de se débloquer. Ça arrive vraiment souvent (moi le premier) d'avoir une petite erreur et la trouver parce qu'un oeil externe y à jeter un coup d'oeil.

Toujours est il que "l'intellisense de Qt" ne me propose pas ma fonction template set() lorsque j'écris mon code c'est peut être ça qui m'a dérouté....

Attention, il faut bien comprendre qu'un template n'est pas un code C++, mais un générateur de code C++. Il est donc assez compliqué de faire de la complétion sur du template, puisqu'il faut compiler et interpréter le code C++ qui serait potentiellement viable, avant de faire de la complétion. Autant dire que c'est rarement faisable aisément, et c'est la raison pour laquelle la complétion sur template ne sera jamais parfaite.

Au fait dans une de tes remarques tu parles de design dangereux. pourquoi cela ?

Je faisais juste allusion à un getter utilisé comme un setter. Un getter renvoi une donnée constante (ou une copie), mais non l'adresse d'une variable. C'est la méthode "set" qui modifie une variable.
Si tu renvoies l'adresse de ta variable, ça équivaut strictement à ne plus avoir d'accesseur, et donc plus d'encapsulation. Dans ce cas autant utiliser une "struct" plutôt qu'une "class", et utiliser tes variables directement. C'est un design valable qui à le mérite d'être plus clair. En gros: soit tu veux faire de l'encapsulation et tu le fais bien, soit tu n'en as pas besoin, et tu utilises une struct avec attributs libres (c'est un choix de design). Généralement, un getter qui renvoie une adresse non constante == souci de design.
(Ou alors peut être que ta classe InputParameter, devrait avoir une méthode "loadFromFile" qui lit le fichier et charge les arguments à partir de celui-ci).

InputParameter::InputParameter(){m_intervalTime = 0; m_tStart = 0;}
Si tu sais que ton interval et ton start seront fixés une bonne fois pour toutes (pas de modification), alors tu peux faire:

class InputParameter
{
public:
  InputParameter(int intervalTime, int tStart);
  int getIntervalTime() const { return m_intervalTime; }
  int gettStart() const { return m_tStart; }

private:
  const int m_intervalTime;
  const int m_tStart;
};

InputParameter::InputParameter(int intervalTime, int tStart)
 : m_intervalTimeintervalTime), tStart(m_tStart)
{
}


Sinon, ce que tu propose est bon (je conseille néanmoins l'utilisation d'une liste d'initialisation, plutôt que d'assigner dans le corps du constructeur).

__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
Commenter la réponse de cptpingu
Messages postés
642
Date d'inscription
mercredi 10 septembre 2008
Statut
Membre
Dernière intervention
9 février 2017
4
0
Merci
Re,

Bon pour être honnête ma première version est avec une structure dès le départ. Mais je ne suis pas un inconditionnel de l'utilisation des struct dans un design OO. J'ai donc tenter de remplacer ma struct par une classe. Et oui avec ma structure effectivement ma méthode set() me stocker dans param.getIntervalTime la valeur lue dans mon fichier.

Mais tu as complètement raison en ce qui concerne mon getter getIntervalTime() par exemple. Mais du coup je vois bien le principe, je récupère la valeur dans mon fichier texte et je la "setIntervalTime()" dans mon objet param et ensuite je peux utiliser ces valeurs dans mon main.cpp avec mon getIntervalTime().
Mais je ne vois pas comment faire pour utiliser un setter puisqu'il n'a pas de type de retour (void) et me dit du coup : invalide use of void expression si j'utilise un setter.

++ dodo
Commenter la réponse de dodo7263
Messages postés
3835
Date d'inscription
dimanche 12 décembre 2004
Statut
Modérateur
Dernière intervention
10 juin 2019
84
0
Merci
Bon pour être honnête ma première version est avec une structure dès le départ. Mais je ne suis pas un inconditionnel de l'utilisation des struct dans un design OO.

Le c++ n'est pas un langage OO, mais un langage multi-paradigme, autant en profiter. Ça m'arrive souvent d'utiliser des structures, si je n'ai que des datas à mapper et pas d'opérations à réaliser dessus. Je ne dis pas qu'il faut que tu utilises des struct partout, mais simplement qu'il ne faut pas se priver si c'est plus adapté qu'une classe. Il ne faut pas se borner à appliquer un modèle OO pur, c'est parfois plus lourd que nécessaire, et pas forcément plus propre.

Mais je ne vois pas comment faire pour utiliser un setter puisqu'il n'a pas de type de retour (void) et me dit du coup : invalide use of void expression si j'utilise un setter.

Plusieurs solutions:

1) Si tu veux garder ton code tel quel:
std::ifstream& operator>>(std::ifstream &in, InputParameter& param)
{
    ini init(in);
    int tmp;
    init.Set("TSTART", tmp);
   param.setIntervalTime(tmp);
    return in;
}


2) En revanche, si tu ne veux pas de variable temporaire, tu peux passer cette méthode en "friend" de ta classe et l'utiliser comme si ces champs était public (moyen, friend == mauvais design dans 99% des cas).

3) Tu peux aussi créer une méthode redéfinissant operator>> au sein de ta classe (pas terrible, la méthode 1 est plus propre):
std::ifstream&
InputParameter::operator>>(std::ifstream &in)
{
    ini init(in);
    init.Set("TSTART", m_intervalTime);
    return in;
}


4) Tu peux considérer que InputParameter n'a pas de méthodes, que des attributs, et qu'une struct est alors plus adaptée.

5) Tu laisses InputParameter en forme de classe, et tu lui ajoutes la méthode "bool loadFromFile(const std::string& filename)", qui remplit ses attributs à partir d'un fichier.

Au choix !
__________________________________________________________________________________________________
Améliorez votre expérience CodeS-SourceS avec ce plugin:
http://codes-sources.commentcamarche.net/forum/affich-10000111-plugin-better-cs-2#cptpingu-signature
Commenter la réponse de cptpingu