Je veux écrire une version insensible à la casse d'un modèle d'une fonction de recherche qui fait quelque chose comme ceci:
template<typename _Tvalue> auto findKeyIC(std::map<char*, _Tvalue>& map, const char* k) ... template<typename _Tvalue> auto findKeyIC(std::map<wchar_t*, _Tvalue>& map, const wchar_t* k) ...
Le but ici est de rechercher un conteneur de paires valeur / clé, où la clé est un string (char *, wchar_t *, std :: string ou std :: wstring) et renvoie l'entrée correspondante ou cend si aucune correspondance.
Comment puis-je définir cela comme un modèle qui gère tous les différents cas ? Puis-je le faire avec un seul modèle? Puis-je le faire avec 4 modèles explicites d'une manière ou d'une autre?
J'ai essayé:
#define TKEYSTR char* #define TVALUE int typedef std::map<TKEYSTR, TVALUE> tStrMap; auto findKeyIC(const tStrMap& map, const TKEYSTR k) { using TIterator = tStrMap::const_iterator; TIterator it; for (it = map.cbegin(); it != map.cend(); map++) { if (boost::iequals(map.first, k)) return it; } return it; }
Mais ce n'est pas autorisé car je déclare le même modèle deux fois .
Enfin, est-il possible de faire en sorte que cela fonctionne pour une plus large gamme de conteneurs que la simple carte? c'est-à-dire tout conteneur qui a une clé qui est une forme de chaîne?
Merci
Jules
3 Réponses :
Vous définiriez votre fonction comme modèle en parallèle du modèle de la carte:
template <typename Key, typename Value, template<typename, typename> class Container> auto findKey(Container<Key, Value> const& data, Key const& key);
Pour les conteneurs arbitraires, vous pouvez ajouter un paramètre de modèle de modèle:
template <typename Key, typename Value> auto findKey(std::map<Key, ValueType> const& data, Key const& key);
Merci, cela m'a permis de créer une réponse à la question, même si je n'ai réalisé qu'après coup que iequals ne gère pas char * etc donc j'ai dû me spécialiser.
Vous pouvez inverser le problème et créer un foncteur de comparaison personnalisé qui effectue une comparaison insensible à la casse. Cela rendrait la recherche beaucoup plus rapide et plus facile à utiliser (car vous n'avez pas besoin de compter sur une fonction externe). Cela pourrait ressembler à quelque chose comme ceci.
template <typename String> struct ILess { bool operator()(const String &lhs, const String &rhs) const noexcept { return boost::ilexicographical_compare(lhs, rhs); } }; template <typename String> struct IEqualTo { bool operator()(const String &lhs, const String &rhs) const noexcept { return boost::iequals(lhs, rhs); } }; template <typename String> struct IHash { size_t operator()(const String &str) const noexcept { size_t hash = 0; for (const auto ch : str) { boost::hash_combine(hash, std::toupper(ch)); } return hash; } }; template <typename Key, typename Value> using IMap = std::map<Key, Value, ILess<Key>>; template <typename Key, typename Value> using IUnorderedMap = std::unordered_map<Key, Value, IHash<Key>, IEqualTo<Key>>;
Bien sûr, si vous devez effectuer des recherches sensibles à la casse et à la casse dans le même conteneur, cela ne fonctionnera pas.
Bonne solution. J'ai trouvé des exemples similaires de le faire de cette façon ailleurs, mais comme vous le mentionnez, cela change le comportement pour toutes les opérations, et pour le moment je veux juste l'insensibilité à la casse pour des opérations spécifiques.
À quoi sert le préfixe «I»? Habituellement, je le vois sur des classes servant de types d'interface, ayant ainsi des fonctions virtuelles pures. Mais les vôtres sont des classes entièrement implémentées et même pas polymorphes, donc évidemment autre chose ...
@Aconcagua "I" signifie "insensible". J'ai oublié cette convention de dénomination parce que je ne l'utilise pas.
Ah, j'aurais pu penser à moi-même ... Eh bien, je n'apprécie pas non plus cette convention de dénomination et j'évite aussi la notation hongroise, si elle n'est pas obligée de l'utiliser par certaines directives de codage. C'est toujours aussi courant que c'est la première chose qui me vient à l'esprit ...
Sur la base de la réponse de @ Aconcagua, j'ai écrit le code suivant qui résout le problème. J'ai dû spécialiser les fonctions char * et wchar * car boost iequal ne fonctionne que sur les chaînes.
template<typename Key> bool IsKeyEqualI(const Key & str1, const Key & str2) { return boost::iequals(str1, str2); } bool IsKeyEqualI(const char *const& str1, const char *const& str2) { return (_stricmp(str1, str2) == 0); } bool IsKeyEqualI(const wchar_t *const& str1, const wchar_t *const& str2) { return (_wcsicmp(str1, str2) == 0); } template <typename Key, typename Value, typename Reference, template<typename ...> class Container> auto findKeyIC(Container<Key, Value> & container, Reference const & key) { auto it = container.begin(); for (; it != container.end(); ++it) { if (IsKeyEqualI(it->first, key)) return it; } return it; }
MISE À JOUR: Le code ci-dessus ne fonctionne pas tout à fait, basé sur la suggestion, ce qui suit est à la fois plus propre et plus générique. Un tas d'autres corrections et suggestions d'Aconcagua ont finalement abouti à ce que toutes les versions fonctionnent, j'ai eu beaucoup de mal à faire accepter une chaîne littérale, mais c'est bien aussi maintenant.
template <typename Key, typename Value> auto findKeyIC(std::map<Key, Value> const& map, Key const& key) { using TIterator = std::map<Key, Value>::const_iterator; TIterator it; for (it = map.cbegin(); it != map.cend(); it++) { if (boost::iequals(it->first, key)) return it; } return it; } template <typename Value> auto findKeyIC(std::map<char*, Value> const& map, char* const& key) { using TIterator = std::map<char*, Value>::const_iterator; TIterator it; for (it = map.cbegin(); it != map.cend(); it++) { if (_stricmp(it->first, key) == 0) return it; } return it; } template <typename Value> auto findKeyIC(std::map<wchar_t*, Value> const& map, wchar_t* const& key) { using TIterator = std::map<wchar_t*, Value>::const_iterator; TIterator it; for (it = map.cbegin(); it != map.cend(); it++) { if (_wcsicmp(it->first, key) == 0) return it; } return it; }
Vous pouvez toujours combiner cela avec un paramètre de modèle de modèle pour accepter des types de mappage arbitraires ...
Je viens de travailler dessus mais j'ai rencontré un problème où il ne reconnaît pas mon modèle.C'est ce que j'ai essayé mais lorsque je l'invoque, j'obtiens des erreurs: template
Je ne spécialiserais pas toute la fonction. Au lieu de cela, j'aurais une fonction de comparaison distincte dont vous avez des spécialisations. Vous dupliqueriez moins de code de cette façon.
En dehors de cela, vous pouvez simplifier un peu votre code: auto it = map.begin (); for (; it! = end; ++ it) {}
, en évitant d'avoir à spécifier le type explicitement. (Note latérale: sur une carte const, begin et cbegin sont tous les mêmes, il n'y a qu'une différence sur les cartes non const.)
Je vois ... Au début, tout ce dont vous avez besoin, ce sont des fonctions de comparaison séparées. Le plus simple est d'avoir des surcharges au lieu de spécialisations de modèles: bool equalsCI (char const * x, char const * y); booléen est égal à CI (wchar_t const * x, wchar_t const * y); booléen est égal à (std :: string const & x, std :: string const & y);
Ensuite, nous avons un problème avec les spécialisations de pointeurs: template
déduit à findKey (std :: map
, ce qui fait que le pointeur est const, pas les données pointées!
Je pense que le problème peut être dû au fait que le modèle std :: map prend en fait 4 arguments. J'obtiens une erreur: impossible de déduire l'argument de modèle pour 'const Container
Pour y parvenir, nous avons malheureusement besoin d'une autre surcharge: template
char const *
vs char * const
avant.
Avec quelle version standard compilez-vous? Pas tout à fait sûr, mais instancier des modèles qui attendent moins de paramètres avec d'autres en ayant plus, mais ceux par défaut, n'est possible que depuis C ++ 17 (peut-être C ++ 14?). Si vous avez un compilateur qui utilise par défaut une norme plus ancienne (ou configuré pour utiliser une norme plus ancienne via des indicateurs de compilateur), vous devrez ajouter ces paramètres de modèle supplémentaires.
Vous pouvez essayer avec
Modèle
Char Const * const & code> pour les littéraux de chaîne, tandis que le paramètre de carte peut rester
char * code>.
Oh, note latérale: la carte standard comparera les valeurs de pointeur pour char *
et wchar_t *
, pas les chaînes elles-mêmes. Vous devrez donc quand même ajouter votre comparateur personnalisé! Essayez ceci pour voir: std :: map
@Aconcagua J'ai mis à jour la réponse ci-dessus avec une version du code qui semblait fonctionner. Je m'en tiens à C ++ 11 pour le moment, donc j'avais besoin du ... et de divers autres ajustements. Vos commentaires, ainsi que la suppression de tous les const, puis leur ajout petit à petit, m'ont finalement apporté la réponse. Merci beaucoup pour votre patience et vos idées.
Les identificateurs commençant par un trait de soulignement immédiatement suivi d'un caractère majuscule sont réservés en C ++.