4
votes

Surcharge d'opérateur de classe de modèle C ++ avec les mêmes signatures

Une simple question C ++ OO regradant les modèles et la surcharge d'opérateurs: Dans la classe suivante, j'ai surchargé l'opérateur d'index deux fois:

test<int, int> obj;

Maintenant, si j'instancie un objet de cette classe de modèle avec les mêmes noms de types:

template<class A, class B>
class test
{
  A a1;
  B a2;
public:
  A& operator[](const B&);
  B& operator[](const A&);
};

l'appel de l'opérateur d'index entraînera une erreur, car les deux fonctions surchargées auront les mêmes signatures.

Existe-t-il un moyen de résoudre ce problème?

Désolé, s'il s'agit d'une question basique. J'apprends encore!


12 commentaires

Vous pouvez utiliser std :: enable_if et uniquement activer la deuxième surcharge si les types A et B ne sont pas les mêmes


Je pense que c'est plus un problème de conception / d'analyse / d'exigences, et que vous devez revenir à la planche à dessin pour éviter de tels problèmes. Oui, vous pouvez faire des "hacks" pour atténuer ou contourner le problème dans le code lui-même, mais ces solutions de contournement ont tendance à être moins optimales et rendent le code beaucoup plus difficile à comprendre et (plus important) à maintenir.


Ce qui serait typename std :: enable_if , B> :: type operator [] (const A &); IIRC.


Lors de l'indexation, comment allez-vous différencier les deux surcharges? c'est-à-dire que doit appeler test [5] ? La première ou la deuxième fonction d'indexation?


Vous n'avez pas fourni de contexte, mais comme le suggère certains programmeurs ... vous avez probablement besoin de deux fonctions bien nommées, pas de surcharges d'opérateurs.


Il s'agit d'un x y problème . Qu'essayez-vous de faire?


@MatthieuBrucher Pourquoi ne pas en faire une réponse à ce stade?


@FantasticMrFox C'est ce que je voulais faire: Disons que la classe a deux tableaux membres de la même taille (A * a1, B * a2), je voulais avoir deux opérateurs d'index qui prennent un élément d'un tableau et renvoie le élément correspondant de l'autre tableau (carte)


@MaxLanghof parce que, comme d'autres l'ont dit, il y a des points obscurs, cela résout la question directe OP, mais je ne suis pas sûr que ce soit la bonne réponse au problème réel. Et vu le dernier commentaire, cela ne l'aurait pas résolu apparemment.


Si vous ne voulez pas carrément interdire le cas A == B , alors vous avez besoin d'une manière différente de faire la distinction entre "donnez-moi le A / B correspondant". Une fonction nommée ( getCorrespondingA (const B & b) ) ferait totalement l'affaire - est-il nécessaire d'utiliser operator [] ?


@MaxLanghof oui, on m'a demandé de le faire en utilisant l'opérateur []. Merci


Pouvez-vous donc clarifier comment myTest [1] doit se comporter? Doit-il donner la valeur de A ou B ? Ou est-ce peut-être correct d'interdire le cas A == B ?


4 Réponses :


5
votes

Vous pouvez ajouter une spécialisation partielle:

template<class A>
class test<A, A>
{
  A a1, a2;
public:
  A& operator[](const A&);
};


2 commentaires

C'est techniquement un moyen, mais vous devrez probablement dupliquer tout ce que fait la classe. Obtient toujours un up de moi.


@MaxLanghof Habituellement, la duplication de code peut être évitée en héritant d'une base commune.



2
votes

Vous pouvez éviter ce problème et rendre le code plus robuste et expressif en convertissant l'index en un autre type qui clarifie ce que l'utilisateur veut. L'utilisation serait comme ceci:

template<class TKey, class TValue>
class bidirectional_map
{
  TKey a1;   // Probably arrays
  TValue a2; // or so?
public:
  TValue& getValueForKey(const TKey& key) { /* ... */ }
  TKey& getKeyForValue(const TValue& value) { /* ... */ }
};

Implémenté comme ceci:

template<class TKey>
struct Key { const TKey& key; };

template<class TValue>
struct Value { const TValue& value; };

// Deduction guides (C++17), or use helper functions.
template<class TValue>
Value(const TValue&) -> Value<TValue>;
template<class TKey>
Key(const TKey&) -> Key<TKey>;

template<class TKey, class TValue>
class bidirectional_map
{
  TKey a1;   // Probably arrays
  TValue a2; // or so?
public:
  TValue & operator[](Key<TKey> keyTag) { const TKey & key = keyTag.key; /* ... */ }
  TKey & operator[](Value<TValue> valueTag) { const TValue& value = valueTag.value; /* ... */ }
};

Maintenant, Key et Les valeurs sont des noms populaires, donc les avoir "repris" par ces fonctions auxiliaires n'est pas le meilleur. De plus, ce n’est qu’un exercice assez théorique, car les fonctions membres sont bien sûr bien plus adaptées à cette tâche:

bidirectional_map<int, int> myTest;
int& valueFor1 = myTest[Key{1}];
int& key1 = myTest[Value{valueFor1}];


0 commentaires

1
votes

Dans C ++ 2a, vous pouvez utiliser requires pour "ignorer" la fonction dans certains cas:

template<class A, class B>
class test
{
    A a1;
    B a2;
public:
    A& operator[](const B&);
    B& operator[](const A&) requires (!std::is_same<A, B>::value);
};

Démo


0 commentaires

1
votes

Voici un exemple de solution utilisant if constexpr qui nécessite C ++ 17:

#include <type_traits>
#include <cassert>
#include <string>

template <class A, class B> 
class test
{
  A a1_;
  B b1_;

public:

    template<typename T> 
    T& operator[](const T& t)
    {
        constexpr bool AequalsB = std::is_same<A,B>(); 
        constexpr bool TequalsA = std::is_same<T,A>();

        if constexpr (AequalsB)
        {
            if constexpr (TequalsA) 
                return a1_;  // Can also be b1_, same types;

            static_assert(TequalsA, "If A=B, then T=A=B, otherwise type T is not available.");
        }

        if constexpr (! AequalsB)
        {
            constexpr bool TequalsB = std::is_same<T,B>();

            if constexpr (TequalsA)
                return a1_; 

            if constexpr (TequalsB)
                return b1_; 

            static_assert((TequalsA || TequalsB), "If A!=B, then T=A || T=B, otherwise type T is not available.");
        }
    }

};

using namespace std;

int main()
{
    int x = 0;  
    double y = 3.14; 
    string s = "whatever"; 

    test<int, int> o; 
    o[x]; 
    //o[y]; // Fails, as expected.
    //o[s]; // Fails, as expected

    test<double, int> t; 
    t[x]; 
    t[y]; 
    //t[s]; // Fails, as expected.

    return 0; 

};


8 commentaires

Notez que OP renvoie l'autre type. L'utilisation de decltype (auto) résoudrait ce problème.


@ Jarod42: Je ne comprends pas votre commentaire, vous voulez dire decltype (auto) sur le type de retour du modèle de fonction membre? Pourquoi est-ce nécessaire, les parties constexpr avec la déduction de l'argument du modèle ne garantissent-elles pas que le bon T est remplacé dans le retour ( T & )?


Votre code fonctionne, mais dans le cas de OP, ce n'est pas T & operator [] (const T & t) mais " TRes & operator [] (const T & t) " (avec < code> TRes = std :: conditional_t ​​ :: value, B, A> ). Donc, utilisez même des traits pour le type de retour, ou laissez le compilateur décider.


Hm, OK ... OP utilise A & operator [] (const B &); B & opérateur [] (const A &); directement. Mon code décide de A & ou B & en utilisant d'abord ADL, puis le compilateur entre dans le corps de la fonction et utilise if constexpr pour choisir la variable de retour. OP m'a demandé de renvoyer A ou B pour différents types, et dans mon code, il / elle peut décider quoi renvoyer si A == B . Je ne vois toujours pas la nécessité de decltype (auto) dans le retour, car tous les cas sont couverts.


Premièrement, il n'y a pas d'ADL ici. Deuxièmement, le code d'OP permet test t; std :: string s = t [42]; , alors que votre code attend int i = t [42]; .


De plus, vous perdez la conversion: test t; t ["c-string"]; n'est pas valide pour vous (nécessite t [std :: string ("c-string")] ).


Les deux problèmes peuvent être résolus, decltype (auto) pour le premier (et la correction de l'implémentation pour renvoyer la valeur OP ;-)), et le second est plus délicat, mais std :: is_same et std :: is_convertible pourraient faire le travail, mais pas sûr que cela donnerait un «meilleur» code que les alternatives.


Déjà dessus, merci. Je vais quand même mettre à jour la réponse, si rien, ça peut être un exemple pour if constexpr je suppose.