0
votes

Comment puis-je créer une fonction basée sur un modèle qui peut fonctionner sur n'importe quel std :: map qui a une clé de chaîne?

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


1 commentaires

Les identificateurs commençant par un trait de soulignement immédiatement suivi d'un caractère majuscule sont réservés en C ++.


3 Réponses :


1
votes

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);


1 commentaires

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.



3
votes

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.


4 commentaires

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 ...



0
votes

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;
}


12 commentaires

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 class Container> auto findKeyIC ( Container const & container, wchar_t * const & key)


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 class Container> auto findKey (Container const & data, Key const & key) déduit à findKey (std :: map , char * const &) , 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 &' from 'std :: map , std :: allocator >> '


Pour y parvenir, nous avons malheureusement besoin d'une autre surcharge: template class Container> auto findKey (Container const & data, Key const * key) < / code>. Notez la différence: celui-ci déduira le deuxième paramètre de 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 au lieu de comme paramètres de modèle de modèle. Sclang et GCC (avec la fois avec -std = C ++ 11) acceptent au moins. Et vous pouvez éviter une nécessité pour deux surcharges si vous donnez le paramètre de recherche son propre type. Donc, au total: Modèle Conteneur de classe> Auto FindCey (Conteneur Const et données, Conscons-clefs) Ce sera déduire la clé de Char Const * const & pour les littéraux de chaîne, tandis que le paramètre de carte peut rester char * .


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 m; char h1 [] = "h"; char h2 [] = "h"; m [h1] = 10; m [h2] = 12; std :: cout << m.size () << std :: endl;


@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.