38 votes

Le libre opérateur -> * surcharge-t-il le mal?

Je parcourais la section 13.5 après réfutant l'idée que construit-dans les opérateurs ne pas participer à la résolution de surcharge, et a remarqué qu'il n'y a pas de section sur operator->*. C'est juste un générique opérateur binaire.

Ses frères, operator->, operator*, et operator[], sont tous obligés d'être non-fonctions membres statiques. Cela s'oppose à la définition d'une fonction libre de surcharge d'un opérateur couramment utilisé pour obtenir une référence d'un objet. Mais le rare operator->* est à gauche.

En particulier, operator[] a de nombreuses similitudes. Il est binaire (ils ont raté une occasion en or de faire n-aire), et il accepte un certain type de conteneur sur la gauche et une sorte de repérage sur la droite. Particulière-section règles, 13.5.5, ne semble pas avoir d'effets réels à l'exception de hors la loi gratuit fonctions. (Et cette restriction, même s'oppose à l'appui de la commutativité!)

Ainsi, par exemple, c'est parfaitement légal:

#include <utility>
#include <iostream>
using namespace std;

template< class T >
T &
operator->*( pair<T,T> &l, bool r )
    { return r? l.second : l.first; }

template< class T >
 T & operator->*( bool l, pair<T,T> &r ) { return r->*l; }

int main() {
        pair<int, int> y( 5, 6 );
        y->*(0) = 7;
        y->*0->*y = 8; // evaluates to 7->*y = y.second
        cerr << y.first << " " << y.second << endl;
}

Il est facile de trouver des utilisations, mais la syntaxe alternative tend à ne pas être si mauvais que ça. Par exemple, l'échelle des indices pour vector:

v->*matrix_width[2][5] = x; // ->* not hopelessly out of place

my_indexer<2> m( v, dim ); // my_indexer being the type of (v->*width)
m[2][5] = x; // it is probably more practical to slice just once

L'a fait le comité des normes de l'oublier pour éviter cela, était-il trop laid pour la peine, ou il y a les cas d'utilisation réels?

20voto

Kietz Points 352

Le meilleur exemple que je suis conscient de est coup de pouce.Phoenix, qui surcharge l'opérateur à mettre en œuvre paresseux de l'accès des membres.

Pour ceux qui ne connaissent Phoenix, il est extrêmement astucieux de la bibliothèque pour la construction des acteurs (ou des objets de fonction) qui ont l'apparence normale des expressions:

( arg1 % 2 == 1 )     // this expression evaluates to an actor
                 (3); // returns true since 3 % 2 == 1

// these actors can also be passed to standard algorithms:
std::find_if(c.begin(), c.end(), arg1 % 2 == 1);
// returns iterator to the first odd element of c

Il réalise ci-dessus par une surcharge, operator% et operator==. - appliqué à l'acteur, arg1 de ces opérateurs, de retour d'un autre acteur. Le nombre d'expressions qui peuvent être construits de cette manière est extrême:

// print each element in c, noting its value relative to 5:
std::for_each(c.begin(), c.end(),
  if_(arg1 > 5)
  [
    cout << arg1 << " > 5\n"
  ]
  .else_
  [
    if_(arg1 == 5)
    [
      cout << arg1 << " == 5\n"
    ]
    .else_
    [
      cout << arg1 << " < 5\n"
    ]
  ]
);

Après avoir été à l'aide de Phoenix, pour un court moment (non pas que vous jamais revenir en arrière) vous essayez quelque chose comme ceci:

typedef std::vector<MyObj> container;
container c;
//...
container::iterator inv = std::find_if(c.begin(), c.end(), arg1.ValidStateBit);
std::cout << "A MyObj was invalid: " << inv->Id() << std::endl;

Qui sera un échec, car bien sûr de Phoenix acteurs n'ont pas un membre ValidStateBit. Phoenix obtient autour de cette surcharge operator->*:

(arg1 ->* &MyObj::ValidStateBit)              // evaluates to an actor
                                (validMyObj); // returns true 

// used in your algorithm:
container::iterator inv = std::find_if(c.begin(), c.end(), 
      (arg1 ->* &MyObj::ValidStateBit)    );

operator->*s'arguments sont les suivants:

  • LHS: un acteur de revenir MyObj *
  • RHS: adresse d'un membre

Elle renvoie un acteur qui évalue la LHS et regarde pour le membre en elle. (NB: Vous avez vraiment, vraiment voulez assurez-vous que arg1 retours MyObj * - vous ne l'avez pas vu un énorme modèle d'erreur jusqu'à ce que vous obtenez quelque chose de mal à Phoenix. Ce petit programme a généré 76,738 caractères de la douleur (Boost 1.54, gcc 4.6):

#include <boost/phoenix.hpp>
using boost::phoenix::placeholders::arg1;

struct C { int m; };
struct D { int n; };

int main() {
  ( arg1  ->*  &D::n ) (new C);
  return 0;
}

7voto

Je suis d'accord avec vous qu'il y a une incohérence sur la norme, Il ne permet pas la surcharge d' operator[] avec les fonctions de membre et lui permet de operator->*. De mon point de vue, operator[] est à tableaux en operator->* est pour les structures/classes (une lecture). Les membres d'un tableau sont sélectionnés à l'aide d'un index. Les membres d'une struct sont sélectionnés par les membres de pointeurs.

Le pire, c'est que nous pouvons être tentés de l'utiliser ->* au lieu de operator[] pour obtenir un tableau comme élément

int& operator->*(Array& lhs, int i);

Array a;

a ->* 2 = 10;

Il y a aussi une autre incohérence. Nous pouvons utiliser un non-membre de la fonction de surcharge operator+= et tous les opérateur de la forme @=) et nous ne pouvons pas le faire pour l' operator=.

Je ne sais pas vraiment quel est l'intérêt de faire de l'juridiques suivants

struct X {
    int val;
    explicit X(int i) : val(i) {}
};
struct Z {
    int val;
    explicit Z(int i) : val(i) {}
};
Z& operator+=(Z& lhs, const X& rhs) {
    lhs.val+=rhs.val;
    return lhs;
}

Z z(2);
X x(3);
z += x;

et interdisant

Z& operator=(Z& lhs, const X& rhs) {
    lhs.val=i;
    return lhs;
}

z = x;

Désolé de ne pas répondre à votre question, mais en ajoutant encore plus de confusion.

2voto

Potatoswatter Points 70305

Googler un peu, j'ai trouvé plus d'exemples de gens qui demandent si operator->* est jamais utilisé que des suggestions réelles.

Un couple de lieux suggèrent T &A::operator->*( T B::* ). Pas sûr que cela reflète le concepteur de l'intention, ou une fausse impression qu' T &A::operator->*( T A::* ) est un builtin. Pas vraiment à ma question, mais donne une idée de la profondeur, j'ai trouvé dans la discussion en ligne et littérature.

Il y avait une mention de "D&E 11.5.4" qui est, je suppose, de la Conception et de l'Évolution de C++. Peut-être que contient un indice. Sinon, je vais juste conclure, c'est un peu inutile laideur qui a été négligé par la normalisation, et la plupart des tous les autres aussi.

Modifier Voir ci-dessous pour une pâte de la D&E devis.

Pour mettre cette quantitativement, ->* est le plus serré de liaison de l'opérateur qui peut être définie par une fonction libre. Tous les postfix-expression et des opérateurs unaires surcharges besoin de la fonction membre non statique signatures. Prochain rang après les opérateurs unaires sont de style C jette, ce qui pourrait être dit correspondent à des fonctions de conversion (operator type()), qui a également ne peut pas être libre de ses fonctions. Puis vient ->*, puis de multiplication. ->* pourrait ressembler [] ou comme %, ils aurait pu de toute façon, et ils ont choisi le chemin de la EEEEEEVIL.

1voto

topright gamedev Points 1011

Standard (Projet de Travail 2010-02-16, § 5.5) dit:

Le résultat d'une ->* expression est un lvalue seulement si son deuxième opérande est une pointeur vers les données de membre. Si la deuxième l'opérande est le pointeur null pour membre la valeur de (4.11), le comportement est undefined.

Vous souhaitez peut-être ce comportement bien défini. Par exemple, vérifier si c'est un pointeur null, et de gérer cette situation. J'ai DONC quess c'est une bonne décision pour une norme permettant ->* surcharge.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X