Salut,
J'essaye de coder une fonction en C++ qui sépare un URI en deux: le protocole, et le reste.
mailto:quux@foo.bar -> ("mailto", "quux@foo.bar")
La fonction doit donc retourner deux valeur. J'ai utilisé pour cela un conteneur std::pair<std::string, std::string>
. Elle prends en argument un std::string
.
namespace URI {
std::pair<std::string,std::string> splitScheme (const std::string & str);
}
Jusque là, pas de problèmes: la fonction était sale mais marchais bien.
Là où ça se gâte, c'est que je ne suis pas sûr de toujours utiliser une std::string
: je voudrais que ma fonction fonctionne aussi avec des std::wstring
et des std::u16string
.
J'ai donc essayé de transformer ma fonction en fonction template:
template <class STR>
std::pair<STR, STR> splitScheme (const STR & str);
Au final, j'ai le code suivant:
// URI.hpp
#ifndef _DEF_URI_HPP_
#define _DEF_URI_HPP_
#include <string>
#include <utility>
#include <iostream>
namespace URI {
template <class STR> std::pair<STR, STR> splitScheme (const STR & str);
}
#endif
// URI.cpp
#include "URI.hpp"
template <class STR>
std::pair<STR, STR> URI::splitScheme (const STR & str) {
typename STR::size_type s = str.find(':');
typename STR::value_type c;
unsigned int i;
// <code sale>
if ((s == STR::npos) || str.empty())
return make_pair("", str);
else {
for (i = 0; i < s; i++) {
c = str.at(i);
if (!((c >= 'A' && c <= 'Z')
|| (c >= 'a' && c <= 'z')
|| ((i >= 1) && (c >= '0' && c <= '9'))
|| ((i >= 1) && (c == '+' || c == '-' || c == '.' ))))
return make_pair("", str);
}
return make_pair(str.substr(0, i), str.substr(s+1));
}
// </code sale>
}
#include "URI.hpp"
#include <string>
#include <iostream>
#include <utility>
int main (int argc, char ** argv) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " uri" << std::endl;
return 1;
}
std::string s(argv[1]);
std::pair<std::string,std::string> p;
p = URI::splitScheme<std::string>(s);
if (!p.first.empty())
std::cout << "Scheme: " << p.first << std::endl;
std::cout << "2nd part: " << p.second << std::endl;
return 0;
}
Au final, quand je compile le code, j'obtiens un message d'erreur de ld:
% g++ URI.cpp main.cpp -o test
/tmp/cc1YnskK.o: In function `main':
main.cpp:(.text+0xb5): undefined reference to `std::pair<std::string, std::string> URI::splitScheme<std::string>(std::string const&)'
collect2: error: ld returned 1 exit status
Si je déclare manuellement le prototype non défini, ça ne fait rien de plus:
template <> std::pair<std::string, std::string> splitScheme (const std::string & str);
Quelqu'un a une idée ? Peut être qu'utiliser un std::pair
n'est pas la bonne
méthode ?
Merci d'avance :-)
# Templates et C++03
Posté par Batchyx . Évalué à 3.
N'oublie pas que les templates génèrent du code pour chaque paramètre template différent. Cela veut dire que si le compilateur n'a pas le code de URI::splitScheme lorsque tu l'utilise, alors il ne pourra pas le générer tout seul. Il faut voir ça un peu comme une macro: il faut qu'elle soit définie pour être utilisable.
en C++03 en tout cas. Normalement, cela n'est plus nécessaire en C++11 (le standard oblige à ce que ça marche dans ton cas), mais gcc ne le supporte pas (ou du moins, pas à ma connaissance).
En C++03, tu à le choix entre :
- Mettre le code de splitScheme dans un header et l'inclure.
- déclarer explicitement quel intanciations tu à besoin dans URI.cpp, et utiliser
export
, sachant que très peu de compilos le supportent (et ceux qui le supportent pensent que c'est une mauvaise idée) à tel point que ça à été enlevé dans C++11.Ouais, donc en gros, tu n'a pas le choix quoi.
[^] # Re: Templates et C++03
Posté par mrr . Évalué à -1.
Ok, merci :-)
Effectivement, en mettant tout le code dans le header, ça marche… mais ce n'est pas très propre…
En fait, puisque les classes
std::u16string
etstd::string
dérivent destd::basic_string
, j'imagine que je peux utiliser cette classe pour ma fonction, et faire du type casting si j'ai besoin destd::u16string
…Envoyé depuis ma Debian avec Firefox
[^] # Re: Templates et C++03
Posté par Batchyx . Évalué à 1.
J'ai du mal à voir en quoi mettre des macro dans les headers est "propre" alors que mettre un métaprogramme template dans les headers n'est "pas très propre". Les deux sont de la métaprogrammation. Ce n'est pas parce que l'un ressemble à une implémentation que ça en est une.
Sinon,
std::string
n'hérite pas destd::basic_string
.std::string
est exactementstd::basic_string<char>
. Il n'y a pas de lien d'héritage entre un patron et son instanciation. En fait, il n'y a pas de lien du tout. On peut très bien avoir un patron:et décider que
B<int>
est totalement différent…(ce n'est pas le cas de
std::basic_string<char>
sur les bonnes implémentations, mais ça pourrai).Pour cette raison, si tu veut à la fois supporter les
std::string
etstd::u32string
, tu n'a pas vraiment le choix que d'utiliser un template. (bon, ok, tu peux utiliserboost::variant
ouboost::any
, mais c'est franchement se tirer une balle dans le pied).# Include à l'envers :-)
Posté par khivapia . Évalué à 2. Dernière modification le 17 décembre 2012 à 10:32.
Dans les template, tout est un header : le fichier .cpp n'est pas compilé en un fichier objet vu qu'il est générique.
Il faut donc inclure le cpp à la fin du hpp, et non pas le hpp au début du cpp. (et éventuellement renommer le cpp en tpp pour préciser qu'il s'agit d'un template ?)
(après c'est peut-être une méthode très sale, si quelqu'un a mieux je suis preneur).
[^] # Re: Include à l'envers :-)
Posté par serge_sans_paille (site web personnel) . Évalué à 1.
find /usr/include/c++/4.7
(et une paire d'yeux aguerris) nous informe que gcc stocke ça sous forme de.tcc
.# 3 solutions
Posté par Gof (site web personnel) . Évalué à 1.
Comme dit plus haut le compilateur doit être capable d'instancier les template pour les type particuler.
Je propose 3 solutions:
1- Comme déjà proposé, mettre l'implémentation dans le fichier entête (URI.hpp)
2- Si tu sais à l'avance quels type seront utilisé, tu peux faire une instentiation explicite dans URI.cpp
Cela dit au compilateur de générer le code pour ton template pour les type donnés lors de la compilation de URI.cpp, et permettra au linkeur de les trouver lors de l'édition des liens.
3- export template: dans ton header rajoute le mot clef export
Mais oublie cette dernière technique, car quasiment aucun compilateur ne supporte ce mot clef. Et il a d'ailleurs été retiré en C++11:
http://en.wikipedia.org/wiki/C++#Exported_templates
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.