web-dev-qa-db-fra.com

Quand surcharger l'opérateur virgule?

Je vois des questions sur SO de temps en temps sur la surcharge de l'opérateur virgule en C++ (principalement sans rapport avec la surcharge elle-même, mais des choses comme la notion de points de séquence), et cela me fait me demander:

Quand devrait vous surcharger la virgule? Quels sont quelques exemples de ses utilisations pratiques?

Je ne peux tout simplement pas penser à des exemples du haut de ma tête où j'ai vu ou besoin de quelque chose comme

foo, bar;

dans le code du monde réel, donc je suis curieux de savoir quand (si jamais) cela est réellement utilisé.

63
Mehrdad

Modifions un peu l'accent sur:

Quand vous surcharger la virgule?

La réponse: jamais.

L'exception: si vous effectuez une métaprogrammation de modèle, operator, a une place spéciale tout en bas de la liste de priorité des opérateurs, ce qui peut être utile pour la construction de gardes SFINAE, etc.

Les deux seules utilisations pratiques que j'ai vues de la surcharge operator, sont tous les deux en Boost :

  • Boost.Assign
  • Boost.Phoenix - il est fondamental ici car il permet à Phoenix lambdas de prendre en charge plusieurs instructions
60
ildjarn

J'ai utilisé l'opérateur virgule pour indexer des cartes avec plusieurs index.

enum Place {new_york, washington, ...};

pair<Place, Place> operator , (Place p1, Place p2)
{
    return make_pair(p1, p2);
}


map< pair<Place, Place>, double> distance;

distance[new_york, washington] = 100;
132
Petter

Boost.Assign l'utilise, pour vous permettre de faire des choses comme:

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

Et je l'ai vu utilisé pour des hacks de langue originaux, je vais voir si je peux en trouver.


Aha, je me souviens de l'une de ces utilisations originales: collecte de plusieurs expressions . (Attention, magie noire.)

34
GManNickG

La virgule a une propriété intéressante en ce qu'elle peut prendre un paramètre de type void. Si c'est le cas, alors l'opérateur de virgule intégré est utilisé.

C'est pratique lorsque vous voulez déterminer si une expression a le type void:

namespace detail_
{
    template <typename T>
    struct tag
    {
        static T get();
    };

    template <typename T, typename U>
    tag<char(&)[2]> operator,(T, tag<U>);

    template <typename T, typename U>
    tag<U> operator,(tag<T>, tag<U>);
}

#define HAS_VOID_TYPE(expr) \
    (sizeof((::detail_::tag<int>(), \
             (expr), \
             ::detail_::tag<char>).get()) == 1)

Je laisse le lecteur comprendre en exercice ce qui se passe. N'oubliez pas que operator, s'associe à droite.

23
Alexandre C.

Similaire à @ GMan'sBoost.Assign exemple, Blitz ++ surcharge l'opérateur virgule pour fournir une syntaxe pratique pour travailler avec tableaux multidimensionnels. Par exemple:

Array<double,2> y(4,4);   // A 4x4 array of double
y = 1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1;
13
Josh Kelley

Dans SOCI - The C++ Database Access Library il est utilisé pour l'implémentation de la partie entrante de l'interface:

sql << "select name, salary from persons where id = " << id,
       into(name), into(salary);

De la justification de la FAQ :

Q: L'opérateur virgule surchargé n'est que de l'obscurcissement, je n'aime pas ça.

Eh bien, considérez ce qui suit:

"Envoyer la requête X au serveur Y et mettre le résultat dans la variable Z."

Ci-dessus, le "et" joue un rôle de virgule. Même si la surcharge de l'opérateur virgule n'est pas une pratique très courante en C++, certaines bibliothèques le font, réalisant une syntaxe concise et facile à apprendre. Nous sommes à peu près sûrs que dans SOCI, l'opérateur de virgule a été surchargé avec un bon effet.

9
moooeeeep

Une possibilité est la bibliothèque Boost Assign (même si je suis sûr que certaines personnes considéreraient cet abus plutôt que comme une bonne utilisation).

Boost Spirit surcharge probablement aussi l'opérateur virgule (il surcharge presque tout le reste ...)

6
Jerry Coffin

Dans le même ordre d'idées, j'ai reçu une demande d'extraction github avec surcharge d'opérateur virgule. Cela ressemblait à quelque chose comme

class Mylogger {
    public:
            template <typename T>
            Mylogger & operator,(const T & val) {
                    std::cout << val;
                    return * this;
            }
 };

 #define  Log(level,args...)  \
    do { Mylogger logv; logv,level, ":", ##args; } while (0)

alors dans mon code je peux faire:

 Log(2, "INFO: setting variable \", 1, "\"\n");

Quelqu'un peut-il expliquer pourquoi il s'agit d'un bon ou d'un mauvais cas d'utilisation?

5
AC.

L'un des usages pratiques est de l'utiliser efficacement avec des arguments variables en macro. Soit dit en passant, les arguments variables étaient auparavant une extension de GCC et font désormais partie de la norme C++ 11.

Supposons que nous ayons un class X, qui y ajoute un objet de type A. c'est à dire.

class X {
  public: X& operator+= (const A&);
};

Que faire si nous voulons ajouter un ou plusieurs objets de A dans X buffer;?
Par exemple,

#define ADD(buffer, ...) buffer += __VA_ARGS__

Au-dessus de la macro, si utilisée comme:

ADD(buffer, objA1, objA2, objA3);

il se développera ensuite:

buffer += objA1, objeA2, objA3;

Par conséquent, ce sera un exemple parfait d'utilisation de l'opérateur virgule, car les arguments variables se développent avec le même.

Donc, pour résoudre ce problème, nous surchargeons l'opérateur comma et l'enveloppons autour de += comme ci-dessous

  X& X::operator, (const A& a) {  // declared inside `class X`
    *this += a;  // calls `operator+=`
  }
4
iammilind

J'utilise l'opérateur virgule pour imprimer la sortie du journal. Il est en fait très similaire à ostream::operator<< mais je trouve en fait l'opérateur virgule mieux pour la tâche.

Donc j'ai:

template <typename T>
MyLogType::operator,(const T& data) { /* do the same thing as with ostream::operator<<*/ }

Il a ces belles propriétés

  • L'opérateur virgule a la priorité la plus basse. Donc, si vous voulez diffuser une expression, les choses ne se gâchent pas si vous oubliez la parenthèse. Comparer:

    myLog << "The mask result is: " << x&y; //operator precedence would mess this one up
    myLog, "The result is: ", x&y;
    

    vous pouvez même mélanger les opérateurs de comparaisons à l'intérieur sans problème, par exemple.

    myLog, "a==b: ", a==b;
    
  • L'opérateur virgule est visuellement petit. Il ne gâche pas la lecture en collant beaucoup de choses ensemble

    myLog, "Coords=", g, ':', s, ':', p;
    
  • Il s'aligne sur ce qui signifie de l'opérateur virgule, c'est-à-dire "imprimer ceci" puis "imprimer cela".

3
CygnusX1

Voici un exemple tiré de la documentation OpenCV ( http://docs.opencv.org/modules/core/doc/basic_structures.html#mat ). L'opérateur virgule est utilisé pour l'initialisation de cv :: Mat:

// create a 3x3 double-precision identity matrix
Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
1
Alexey Petrenko