6
votes

Comment rechercher par une touche de pointeur const dans une carte avec des touches de pointeur non const

Le code C ++ suivant ne se compile pas car il transmet un pointeur non-const à une fonction find () qui attend un pointeur const.

#include <map>

std::map<int*, double> mymap;

double myfind(const int * mykey)
{
    return mymap.find(mykey)->second;
}

Y a-t-il un moyen de faire fonctionner la recherche sans changer le type de la carte ou rendre la variable mykey non-const? Après tout, la fonction find () ne modifie pas l'objet pointé, elle compare simplement les pointeurs.


6 commentaires

Utilisez const_cast


@ t.niese Pourquoi changeriez-vous cela? Comment résoudrait-il le problème sous-jacent?


Après de nombreuses années, les défauts des interfaces STL sont toujours là; esp. le typage des conteneurs associatifs, le manque de recherche binaire ...


Pourquoi ne pas changer le type de clé à la place?


@curiousguy pas avec les comparateurs transparents de C ++ 14. C'est la différence entre std :: less et std :: less


@Caleth Vous m'avez orienté dans la bonne direction en mentionnant des comparateurs transparents.


4 Réponses :


0
votes

Essayez const_cast qui vous permet de changer la constance (ou la volatilité) de la variable.

#include <map>

std::map<int*, double> mymap;

double myfind(const int * mykey)
{
    return mymap.find(const_cast<int*>(mykey))->second;
}


2 commentaires

Oui mais const_cast est généralement le signe d'une mauvaise interface


Je ne recommande aucun casting, mais c'est une possibilité;)



1
votes

Une clé dans une carte est sémantiquement immuable, toutes les opérations de carte qui permettent un accès direct aux clés le font en const -qualifiant le type de clé (par exemple value_type est défini comme paire code>).

En cas de type de clé int * , vous obtiendrez un pointeur const vers non-const int ( int * const

), ce qui n'est pas très sympa (ça marche toujours, puisque seul le pointeur value est utilisé comme clé, mais la sémantique de l'immuabilité se dilue, ce qui peut conduire à des bogues).

Au lieu de rejeter constness, changez simplement la map en map .

Ensuite, cela fonctionnera pour les clés const int * ainsi que int * .

#include <map>

std::map<const int*, double> mymap;

double myfind(const int * mykey)
{
    return mymap.find(mykey)->second; // just works
}

double myfind(int * mykey)
{
    return mymap.find(mykey)->second; // also works
}


1 commentaires

La valeur sémantique de la clé ne peut pas être modifiée de manière arbitraire dans un conteneur associatif car le conteneur utilise la comparaison (resp. Le hachage) pour ordonner les objets (resp. Les stocker dans un tableau). Mais la valeur sémantique de la clé ici est la valeur du pointeur. Vous pouvez changer le int sans casser le conteneur.



-1
votes

Vous pouvez avoir un problème de const-correctness. const int * n'est peut-être pas ce que vous pensez . Il s'agit d'un pointeur vers un entier constant . Ce n'est pas le même que le type de clé de votre carte, qui est un pointeur vers un entier (non constant) . Et ni sont identiques à int * const qui est un pointeur constant vers un entier (non constant) . Le problème n'est pas de savoir si la valeur de la clé elle-même est mutable ou immuable, c'est si les éléments sur lesquels vous stockez les pointeurs sont mutables ou immuables.

Par exemple, ceci compile:

std::map<const int *, double> mymap;
double myfind(const int *mykey) {
    return mymap.find(mykey)->second;
}
double myfind2(const int * const mykey) {
    return mymap.find(mykey)->second;
}


4 commentaires

Je comprends la différence entre const int * et int * const . Mais vous avez fait un bon point dans le dernier paragraphe. Nous savons que std :: map :: find () ne modifiera pas l'entier pointé, mais le compilateur ne le sait pas. Et dans cette question, j'ai demandé comment le faire savoir au compilateur.


@bedrorom et la réponse à cela se trouve également dans le dernier paragraphe: const_cast<>


Oui, const_cast<> est une possibilité multiple. Mais rejeter const est n'est pas une bonne pratique . Un comparateur personnalisé (si disponible) peut faire la même chose sans enfreindre les recommandations.


Tout à fait d'accord, ce n'est pas une bonne pratique, mais je pense qu'à moins que vous ne changiez le type de clé de la map , c'est finalement ce que vous faites, quels que soient les obstacles que vous franchissez pour y arriver. Le cœur de ma réponse, qui je pense est importante et valable pour les lecteurs, est que const int * est une chose sémantiquement différente de int * . A cela je n'ai vraiment rien à ajouter ou à apporter; Je regrette que cela n'ait pas plu à l'électeur défavorable.



0
votes

Je pense avoir trouvé une solution, mais elle nécessite des comparateurs transparents C ++ 14.

#include <map>
#include <iostream>

struct CompareIntPtrs
{
    using is_transparent = void; // enabling C++14 transparent comparators

    bool operator()(const int * l, const int * r) const
    {
        return l < r;
    }
};

std::map<int*, double, CompareIntPtrs> mymap;

double myfind(const int * key)
{
    return mymap.find(key)->second;
}

int main()
{
    int x {6};
    mymap[&x] = 66; // inserting to the map
    const int * px = &x; // creating a "const int *" variable

    std::cout << myfind(px) << std::endl; // using "const int *" for finding in map with "int*" keys
    std::cout << mymap.find(px)->second << std::endl; // we could even skip using myfind()
}

Un excellent article sur les comparateurs transparents C ++ 14 peut être trouvé ici . Pour être tout à fait honnête, en ajoutant le comparateur, le type de mymap a légèrement changé ce que je ne voulais pas à l'origine, mais c'est la meilleure solution que j'ai pu trouver.

Si C ++ 14 n'est pas disponible, il y a au moins deux maux parmi lesquels nous pouvons choisir. Le premier est de copier mymap dans un nouveau std :: map dans myfind , ce qui est horriblement inefficace. Le second est de rejeter la constness en utilisant un const_cast (mykey) qui devrait être évité si possible.


0 commentaires