33 votes

Comment fonctionne la syntaxe de l'affectation multiple (a = b) = c ?

Comment une déclaration comme (a = b) = c; travailler en C++, en supposant a , b y c sont int ou tout autre type primitif ?

19 votes

Pourquoi est-ce illégal en Python ? Parce qu'ils sont tous différents les langages de programmation ! C n'est pas C++ n'est pas C ! Pourquoi pensez-vous que différents PLs ont la même sémantique ?

2 votes

@Olaf Ok, oublions le C, pourquoi (a = b) = c est une syntaxe C++ légale ? comment ça marche ?

9 votes

Pouvez-vous expliquer pourquoi vous pensez qu'il ne devrait pas l'être ? Cette question est un peu vide de sens.

57voto

Kerrek SB Points 194696

L'expression d'affectation a = b n'est pas une lvalue en C, mais elle l'est en C++ :

  • C11, 6.5.14 (Opérateurs d'assignation) :

    Un opérateur d'affectation stocke une valeur dans l'objet désigné par l'opérande de gauche. Une expression d'affectation a la valeur de l'opérande de gauche après l'affectation, mais n'est pas une lvalue .

  • C++14, 5.18 [expr.ass] (opérateurs d'assignation et d'assignation composée) :

    L'opérateur d'affectation ( = ) et les opérateurs d'affectation composés regroupent tous de droite à gauche. Tous requièrent une lvalue modifiable comme opérande gauche. et retourner une valeur l se référant à l'opérande de gauche.

Dans l'évolution du C++ à partir du C, plusieurs expressions ont été rendues "lvalue-aware", pour ainsi dire, parce que les lvalues sont beaucoup plus importantes en C++ qu'en C. En C, tout est trivial (trivialement copiable et trivialement destructible, tout cela dans les mots du C++), donc les conversions de lvalue à rvalue (ou "conversions de lvalue", comme le C les appelle) ne sont pas douloureuses. En C++, la copie et la destruction sont des concepts non triviaux, et en faisant en sorte que les expressions préservent le caractère lvalue, on peut éviter beaucoup de copies et de destructions qui n'étaient pas nécessaires au départ.

Un autre exemple est l'expression conditionnelle ( a ? b : c ), qui n'est pas une lvalue en C, mais peut être une lvalue en C++.

Un autre artefact intéressant de cette évolution du langage est que le C a quatre durées de stockage bien définies (automatique, statique, locale au fil de l'eau, dynamique), mais en C++ cela devient plus confus, puisque les objets temporaires sont un concept non trivial en C++ qui demande presque sa propre durée de stockage. (Par exemple, Clang en a une cinquième en interne, Durée de stockage "pleine expression". .) Les temporaires sont bien sûr le résultat de la conversion de lvalue en rvalue, donc en évitant la conversion, il y a une chose de moins à se soucier.

(Veuillez noter que toute cette discussion ne s'applique qu'aux expressions du langage de base respectif. Le C++ dispose également d'une fonctionnalité distincte et sans rapport avec le langage, à savoir surcharge des opérateurs qui produit des expressions d'appel de fonction, qui ont toute la sémantique habituelle des appels de fonction et n'ont rien à voir avec les opérateurs, sauf pour la syntaxe. Par exemple, vous pouvez définir une fonction surchargée operator= qui renvoie une valeur de pr ou void si vous le souhaitez).

8 votes

Serait-ce (a ? b : c) = d; signifie alors qu'en fonction de la valeur de a , d sera attribué à b o c ?

7 votes

@PaulOgilvie : c'est correct . Vous pouvez même faire des choses comme (c ? v1 : v2).clear() . Si l'opérateur conditionnel devait toujours aboutir à une valeur pr, vous ne pourriez jamais affecter les valeurs des opérandes.

8 votes

@KerrekSB pire, (c ? v1 : v2).clear() compilerait bien (parce que nous pouvons appeler non- const sur les temporaires), mais ne font rien en silence. *shudders*

23voto

Pete Becker Points 27371

De manière informelle, en C++, pour les types intégrés, le résultat de la fonction a = b est une référence à a ; vous pouvez attribuer une valeur à cette référence, comme pour toute autre référence. Ainsi, (a = b) = c attribue la valeur de b à a et attribue ensuite la valeur de c à a .

Pour les types définis par l'utilisateur, cela peut ne pas s'appliquer, bien que l'idiome habituel soit qu'un opérateur d'affectation renvoie une référence à l'argument de gauche, de sorte que le comportement des types définis par l'utilisateur imite le comportement des types intégrés :

struct S {
    S& operator=(const S& rhs) {
        return *this;
    }
};

Maintenant, S a, b, c; (a = b) = c; signifie appel a.operator=(b) qui renvoie une référence à a ; puis appeler S::operator= sur ce résultat et c en appelant effectivement a.operator=(c) .

9 votes

Les résultats des expressions ne sont jamais des références. Le résultat de a = b est la valeur a (mais l'effet secondaire de la modification a a déjà eu lieu).

5 votes

@KerrekSB - soupir. C'est pourquoi j'ai dit "officieusement". S'il vous plaît, ne tirez pas cela dans un trou à rats.

8 votes

Oui, oui, je comprends... mais après avoir expliqué vingt fois et plus aux gens que les références rvalue ne sont pas rvalue, j'ai commencé à essayer d'enseigner des informations honnêtes dès le début, dans l'espoir de ne jamais produire de fausses idées en premier lieu. Ce n'est pas une critique de votre réponse, je voulais juste que ce détail soit enregistré quelque part :-)

3voto

Anower Perves Points 605

(a = b) = c est une instruction valide en C++. Ici, '=' fonctionne comme un opérateur d'affectation. Ici, b sera assignée à la valeur de a y c sera assignée à la valeur de a pour la préséance de droite à gauche.

Par exemple :

int a = 5;
int b = 2;
int c = 7;
int answer = (a = b) = c;
cout << answer << endl;

Sortie :

7

0 votes

C'est court sur le pourquoi de la question, qui tourne autour du concept de lvalue ; également, answer est défini, mais n'est pas utilisé.

0 votes

Je viens de montrer "pourquoi" et "comment" cette affirmation est valable. L'opérateur d'assignation fonctionne pour assigner et cette déclaration est valable pour tous les opérateurs d'assignation. Et merci pour la mise à jour concernant la variable 'answer'.

0 votes

Désolé, mais quelqu'un élevé au C (où la construction est illégale) n'obtiendra pas la pourquoi de votre réponse. Du moins, je ne l'ai pas fait.

1voto

anatolyg Points 8076

Ce qui suit est une petite spéculation, alors corrigez-moi si je me trompe.

Lorsqu'ils ont inventé la surcharge d'opérateurs, ils ont dû trouver une forme générale standard d'un opérateur d'affectation pour toute classe. T . Par exemple :

T& T::operator=(T);
T& T::operator=(const T&);

Ici, il renvoie une référence à T au lieu de simplement T pour faire une affectation en trois parties comme x = (y = z) efficace, ne nécessitant pas de copie.

Il pourrait renvoyer un const référence à T ce qui rendrait l'affectation non souhaitée (a = b) = c une erreur. I devinez qu'ils ne l'ont pas utilisé pour deux raisons :

  1. Code plus court - pas besoin d'écrire tout cela const tout le temps (les petits détails de const -correction n'étaient pas claires à l'époque)
  2. Plus de flexibilité - permet des codes comme (a = b).print()print est un non const (parce que le programmeur a été paresseux/ignorant de const -correction)

La sémantique pour les types primitifs (qui ne sont pas class ) ont été en quelque sorte extrapolés, pour donner.. :

int& operator=(int&, int); // not real code; just a concept

Le "type de retour" n'est pas const int& afin de faire correspondre le modèle avec class es. Donc, si le buggy (a = b) = c est valable pour les types définis par l'utilisateur, il devrait l'être également pour les types intégrés, comme l'exige la directive sur la protection des données. Principes de conception du C++ . Et une fois que vous avez documenté ce genre de choses, vous ne pouvez pas les modifier pour des raisons de compatibilité ascendante.

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