web-dev-qa-db-fra.com

Pourquoi les opérateurs surchargés ne peuvent pas être définis en tant que membres statiques d'une classe?

La syntaxe C++ permet de définir des opérateurs surchargés dans la structure/classe comme:

struct X
{
   void operator+(X);
}

ou en dehors de la structure/classe comme:

void operator+(X, X);

mais pas comme

struct X
{
   static void operator+(X, X);
}

Quelqu'un connaît-il les raisons de cette décision? Pourquoi la troisième forme n'est pas autorisée? (MSVC donne une erreur de syntaxe). Peut-être y a-t-il une histoire derrière cela?

p.s. La présence simultanée des première et deuxième définitions crée une ambiguïté:

1>CppTest1.cxx
1>c:\ballerup\misc\cf_html\cpptest1.cxx(39) : error C2593: 'operator +' is ambiguous
1>        c:\ballerup\misc\cf_html\cpptest1.cxx(13): could be 'void B1::operator +(B1 &)'
1>        c:\ballerup\misc\cf_html\cpptest1.cxx(16): or       'void operator +(B1 &,B1 &)'
1>        while trying to match the argument list '(B1, B1)'

Je ne comprends pas pourquoi cette ambiguïté est meilleure qu'entre 1,3 ou 2,3.

38
Kirill Kobelev

Je n'ai aucune connaissance spécifique d'aucune discussion C++ de ce concept, alors n'hésitez pas à l'ignorer.

Mais pour moi, vous avez la question à l'envers. La question devrait être: "pourquoi cette syntaxe serait-elle autorisée?"

Il ne fournit aucun avantage par rapport à la syntaxe actuelle. La version de la fonction membre non statique a le même accès aux membres privés que votre version statique proposée. Ainsi, si vous devez accéder aux éléments privés pour l'implémenter, faites-en simplement un membre non statique, exactement comme vous le faites généralement avec la plupart des membres d'une classe.

Cela ne facilite pas la mise en œuvre d'opérateurs asymétriques (ie: operator+(const X &x, const Y &y)). Si vous avez besoin d'un accès privé pour implémenter ceci, vous aurez toujours besoin d'une déclaration d'ami pour eux dans l'une des classes.

Donc, je dirais que la raison pour laquelle il n’existe pas, c’est que ce n’est pas nécessaire. Entre les fonctions non membres et les membres non statiques, tous les cas d'utilisation nécessaires sont couverts.


Ou, pour le dire autrement:

Les fonctions libres peuvent faire tout ce que le système de fonctions statique peut faire, et plus.

Grâce à l'utilisation de fonctions gratuites, vous pouvez obtenir une recherche dépendante des arguments pour les opérateurs utilisés dans les modèles. Vous ne pouvez pas faire cela avec des fonctions statiques, car celles-ci devraient appartenir à une classe particulière. Et vous ne pouvez pas ajouter à une classe en dehors de la classe, alors que vous pouvez ajouter à un espace de noms. Donc, si vous devez mettre un opérateur dans un espace de noms particulier afin de faire fonctionner un code ADL, vous pouvez le faire. Vous ne pouvez pas faire cela avec des opérateurs de fonctions statiques.

Ainsi, les fonctions libres constituent un sur-ensemble de tout ce que votre système de fonctions statique proposé fournirait. Comme il n'y a aucun avantage à le permettre, il n'y a pas de raison pour le permettre, et par conséquent, ce n'est pas autorisé.


ce qui permettrait d'utiliser des foncteurs sans les instancier?

C'est une contradiction dans les termes. Un "foncteur" est un "objet de fonction". Un type est pas un objet; par conséquent, il ne peut s'agir d'un foncteur. Il peut s'agir d'un type qui, une fois instancié, donnera lieu à un foncteur. Mais le type seul ne sera pas un foncteur.

De plus, pouvoir déclarer Typename::operator() statique ne voudrait pas dire que Typename() ferait ce que vous voulez. Cette syntaxe a déjà une signification réelle: instancier une Typename temporaire en appelant le constructeur par défaut.

Enfin, même si tout cela n'était pas le cas, à quoi cela servirait-il? La plupart des fonctions de modèle qui prennent un appelable de quelque type fonctionnent aussi bien avec un pointeur de fonction qu'avec un foncteur. Pourquoi voudriez-vous restreindre votre interface, pas simplement aux foncteurs, mais aux foncteurs qui ne peut pas ont des données internes? Cela signifie que vous ne pourrez pas passer capturer des lambdas et ainsi de suite.

A quoi sert un foncteur qui ne peut éventuellement contenir un état? Pourquoi voulez-vous forcer l'utilisateur à passer des "foncteurs" qui n'ont pas d'état? Et pourquoi voulez-vous empêcher l'utilisateur de pouvoir utiliser lambdas?

Votre question découle donc d'une hypothèse fausse: même si nous l'avions, elle ne vous donnerait pas ce que vous voulez.

16
Nicol Bolas

Parce qu'il n'y a pas de syntaxe évidente pour appeler un tel opérateur, ce qui voudrait dire qu'il faudrait inventer quelque chose. Considérez les variables suivantes:

X x1;
X x2;

Supposons maintenant que nous utilisions des fonctions de membre normales à la place d'opérateurs - supposons que j'ai changé operator+ en plus dans votre exemple.

Chacun des trois appels ressemblerait à:

x1.plus(x2);
plus(x1, x2);
X::plus(x1, x2);

Maintenant, lorsque vous appelez un opérateur en utilisant +, comment le compilateur peut-il rechercher votre opérateur dans le cadre de X? Il ne peut pas le faire pour des fonctions membres statiques normales et les opérateurs ne bénéficient pas d'une dispense spéciale pour lever l'ambiguïté.

Maintenant, considérez si vous aviez les deux les deuxième et troisième formes déclarées dans votre programme. Si vous avez dit x1 + x2, le compilateur devra soit toujours choisir la fonction free, sinon l'appel sera ambigu. La seule vraie alternative serait quelque chose comme x1 X::+ x2 qui a vraiment l'air moche. Compte tenu de tout cela, je suis sûr que le comité des normes a décidé d'interdire simplement la version statique pour les membres, car tout ce qu'il pourrait accomplir pourrait être accompli avec une fonction sans amis.

13
Mark B

Les fonctions membres statiques peuvent être utilisées pour les utilitaires qui aident une classe mais qui, pour une raison ou une autre, ne sont pas membres. Il est facile d'imaginer que parmi les utilitaires exprimés sous forme de fonctions membres de classe statiques, il peut être utile de disposer d'opérateurs.

Bien sûr, si un opérateur surchargé prend une classe C comme argument principal, il n’ya aucune raison de vouloir qu’il soit un membre statique de la classe C. Il peut s’agir d’un membre non statique, il obtient donc cet argument implicitement.

Cependant, un membre statique de la classe C peut être un opérateur surchargé sur une classe autre que C.

Dites qu'il existe une étendue de fichier operator ==(const widget &, const widget &);. Dans ma classe squiggle, je travaille avec des objets widget, mais je souhaite une comparaison différente pour eux. 

Je devrais être capable de faire une static squiggle::operator == (const widget &, const widget &); pour moi-même.

En classe, vous pouvez facilement appeler ceci:

void squiggle::memb(widget a, widget b)
{
   if (a == b) { ... } // calls static == operator for widgets
}

depuis l'extérieur de la classe, nous ne pouvons l'appeler qu'avec la résolution de portée explicite combinée à la syntaxe d'appel d'opérateur explicite: 

void nonmemb(widget a, widget b)
{
   a == b;  // calls the widget member function or perhaps nonstatic operator
   squiggle::operator ==(a, b); // calls squiggle class' utility
}

Ce n'est pas une mauvaise idée. De plus, nous pouvons le faire avec des fonctions surchargées régulières, mais pas avec des opérateurs. Si la comparaison des widgets est effectuée avec une fonction compare, il peut exister une compare non membre ou un widget::compare et un squiggle::compare prenant widgets.

Donc, le seul aspect de ceci qui n'est pas supporté en C++ est la configuration syntaxique avec les opérateurs.

Peut-être que ce n'est pas une idée suffisamment utile pour justifier un soutien (jusqu'à présent!) Après tout, ce n'est pas quelque chose qui permettrait une réorganisation révolutionnaire d'un programme C++. Mais cela corrigerait une incomplétude dans le langage.

De plus, considérons que les surcharges de classe d'opérateurs new et delete sont implicitement statiques ! Donc, l'incomplétude a déjà une petite exception.

2
Kaz

hmmm ... Je pense à un opérateur statique () qui supprimerait implicitement tous les constructeurs ... Cela nous donnerait une sorte de fonction typée. Parfois, j'aimerais que nous l'ayons en C++.

1
Slava

Je ne suis au courant d'aucun inconvénient direct que permettre l'opérateur statique + pourrait causer (peut-être que penser assez longtemps produira une théorie). Mais je pense au moins que le principe "ne payez pas pour ce que vous n'utilisez pas" déclaré par Bjarne Stroustrup est déjà une assez bonne réponse . Que gagnerez-vous si cet opérateur statique est autorisé sauf pour une syntaxe plus compliquée ( vous devrez écrire "X :: operator +" partout au lieu de "+")?

0
mvidelgauz

Fondamentalement, un opérateur statique de membre de classe n'achète rien par rapport à un membre non statique.

Tout opérateur défini pour une classe doit prendre au moins un argument de ce type de classe.

Un opérateur membre prend cet argument sous la forme du paramètre implicite this.

Un opérateur non membre a un argument explicite de ce type de classe.

L’interface opérateur avec la fonction opérateur n’a aucune importance; lorsque nous appelons a + b, il se charge de générer le code à transmettre à a via le paramètre this ou en tant que paramètre explicitement déclaré. Donc, nous n'exprimons aucune différence de nuance entre statique et non statique quant à la manière dont l'opérateur est utilisé.

Supposons que la nouvelle norme ISO C++ doit soudainement prendre en charge les opérateurs membres statiques. Rapidement, cette exigence pourrait être mise en œuvre par une réécriture source à source selon le schéma suivant:

static whatever class::operator *(class &x) { x.fun(); return foo(x); }

-->

whatever class::operator *() { (*this).fun(); return foo(*this); }

-->

whatever class::operator *() { fun(); return foo(*this); }

Le compilateur réécrit l'opérateur membre static en non statique, supprime le paramètre le plus à gauche et (avec l'hygiène lexicale appropriée w.r.t. shadowing) remplace toutes les références à ce paramètre par l'expression *this (les utilisations inutiles peuvent être supprimées).

Cette transformation est assez simple pour que le programmeur puisse écrire le code de cette manière en premier lieu.

Le mécanisme de définition de la fonction opérateur static est moins puissant. Ce ne peut pas être virtual par exemple, alors que le non statique peut l'être.

0
Kaz

Cela pourrait être la raison.

Parce que chaque operator a besoin d'une ou plusieurs operands. Donc, si nous le déclarons comme static, alors nous ne pouvons pas l'appeler à l'aide d'objets (opérandes). 

Pour l'appeler sur un opérande qui n'est autre qu'un objet, la fonction doit être non statique.

Vous trouverez ci-dessous une condition à remplir lors de la surcharge de fonctions.

  • Il doit avoir au moins un opérande de type défini par l'utilisateur.

Supposons donc que nous déclarions notre fonction de surcharge d’opérateur comme étant statique . La première de ces conditions ne sera pas satisfaite.

Une autre raison est qu'à l'intérieur des fonctions statiques, nous ne pouvons accéder qu'aux membres de données statiques. Mais tout en surchargeant les opérateurs, nous devons accéder à tous les membres de données. Donc, si nous déclarons notre fonction de surcharge d'opérateur comme statique, nous ne pourrons pas accéder à tous les membres de données.

Donc, la fonction de surcharge de l'opérateur doit être un non-static member function.

Mais il y a une exception.

Si nous utilisons une fonction amie pour la surcharge de l'opérateur, elle peut être déclarée comme statique.

0
Narendra