Journal C++, surcharge d'opérateur, ordre d'évaluation

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
17
21
fév.
2020

Demat'iNal,

Un collègue a récemment piqué mon intérêt (ouille) avec ce petit bout de code :

#include <map>
#include <iostream>

int main() {
    std::map<int, int> m;
    m[1] = m.size();
    std::cout << m[1] << std::endl;
    return 0;
}

Qui, compilé par deux version différentes de GCC (9.2 et 5.1) donne deux résultats différents (voir https://godbolt.org/z/4_xVPV).

Qu'en déduire ? Bug dans le compilateur ? Comportement indéfini ? Autre chose ? Des fragments de réponse se cachent dans le titre…

  • # Comportement indéfini

    Posté par  . Évalué à 8.

    m[1] = m.size();

    L'ordre entre m[1] et m.size() n'est pas déterminé. operator= est exécuté en dernier mais ce n'est pas lui qui ajoute l'élément, c'est m[1].

    • [^] # Re: Comportement indéfini

      Posté par  . Évalué à 6.

      Indéfini avant C++17.

      À partir de C++17 c'est défini d'après https://en.cppreference.com/w/cpp/language/eval_order

      Règle 16) Every overloaded operator obeys the sequencing rules of the built-in operator it overloads when called using operator notation
      Et 20) In every simple assignment expression E1=E2 and every compound assignment expression E1@=E2, every value computation and side-effect of E2 is sequenced before every value computation and side effect of E1

      m.size() est évalué en premier, puis m[1]. Le bon résultat est celui retourné par gcc 9.

      • [^] # Re: Comportement indéfini

        Posté par  (site web personnel) . Évalué à 6.

        Il y a même eu une dépêche publiée sur le sujet.

      • [^] # Re: Comportement indéfini

        Posté par  . Évalué à 5.

        Indéfini avant C++17.

        GCC 5.1 : publié en 2015
        GCC 9.2 : publié en 2019
        C++17 a été publié entre les deux, du coup pas anormal que le résultat diffère.

      • [^] # Re: Comportement indéfini

        Posté par  . Évalué à 1.

        Je ne savais que la règle avait changé. C'est quand même étrange de voir = comme un point de séquence.

        Mais là -std=c++11 est passé au deux compilateurs, donc on est bien dans un cas où l'ordre est indéfini, non ?

        • [^] # Re: Comportement indéfini

          Posté par  . Évalué à 3.

          Si la version pre 2017 est indéfinie, ça veut dire qu'un nouveau compilateur peut très bien adopter le comportement 2017, même si le switch de version est inférieur.
          C'est qui est plus logique niveau implémentation algorithmique.

        • [^] # Re: Comportement indéfini

          Posté par  (site web personnel) . Évalué à 3.

          donc on est bien dans un cas où l'ordre est indéfini, non ?

          … Ce qui signifie que le compilateur fait ce qu'il veut.

  • # Sequence point

    Posté par  . Évalué à 4. Dernière modification le 21 février 2020 à 15:05.

    Ce que tu demandes n'est pas lié à la surcharge d'opérateur.

    Le standard précise que tous les effets de bord d'un statement doivent être terminés au point de séquence (ici, le ;). Mais sans préciser l'ordre d'évaluation. Notamment, = n'est pas un point de séquence.

    La ligne

    m[1] = m.size();

    Peut donc soit d'abord évaluer m[1], soit m.size(), avant d'effectuer l'affectation.

    Un exemple classique est i = i++;

    https://en.wikipedia.org/wiki/Sequence_point

  • # Undefined != Unspecified

    Posté par  (site web personnel) . Évalué à 2.

    Ce comportement est plutôt non-spécifié, ce qui est (heureusement) différent d'indéfini !

    D'après un draft du standard pris au hasard, dans la section 3 Terms and definitions[intro.defs]

    3.12[defns.impl.defined]implementation-defined behavior:
    behavior, for a well-formed program construct and correct data, that depends on the implementation and that each implementation documents

    3.27[defns.undefined]undefined behavior
    behavior for which this document imposes no requirements

    3.28[defns.unspecified]unspecified behavior
    behavior, for a well-formed program construct and correct data, that depends on the implementation

    On notera d'ailleurs l'élégant

    3.14[defns.locale.specific]locale-specific behavior
    behavior that depends on local conventions of nationality, culture, and language that each implementationdocuments

    • [^] # Re: Undefined != Unspecified

      Posté par  (site web personnel) . Évalué à 2.

      On est d'accord sur les définitions, mais justement pour l'ordre d'évaluation le standard parle bien de undefined behavior. Sur la version du standard que tu donnes, ce n'est plus le cas car justement ça a été changé en 2017, mais si je prends par exemple ce draft de 2014 page 10, i = i++ + 1 est clairement marqué comme un UB (et oui, c'est flippant).

  • # C++82

    Posté par  . Évalué à -6. Dernière modification le 24 février 2020 à 01:28.

    Raisons de plus pour:
    1. utiliser la surcharge d’opérateur quand y a vraiment aucun autre moyen de rendre le code plus clair
    2. passer a Rust :P (qui n'est pas défini par un standard ISO-ique, je vous l'accorde)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.