Journal Un peu de fun C++esque

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
7
12
oct.
2026

Demat'iNal,

En Python, les arguments d'une fonction sont évalués de gauche à droite. Le code suivant :

def dump(i):
    print(i)

def foo(x, y):
    ...

foo(dump(0), dump(1))

Affichera ainsi un 0 suivi d'un 1.

En C++ (comme en C), nous n'avons pas de telle garantie, si bien que l'affichage est non spécifié : cela peut aussi bien être 0 suivi de 1 que 1 suivi de 0 pour :

int dump(int i) { std::cout << i << std::endl; }
void foo(int,int) {}

foo(dump(0), dump(1))

Mais avec la petite astuce suivante on peut forcer l'ordre d’exécution, car les arguments d'une initializer_list sont ordonnés (cf. https://timsong-cpp.github.io/cppwp/n3337/dcl.init.list#4) (ici en utilisant les deduction guide).

std::apply(foo, std::tuple{0, 1});

Cependant on force alors tous les arguments de foo à être passés par valeur. On peut améliorer ce comportement pour respecter les r-value etc de la façon suivante :

template<class... Ts>
class pack_args {
    std::tuple<Ts...> args;
    public:
    pack_args(Ts... args) : args{std::forward<Ts>(args)...} {}
    std::tuple<Ts...> get() && {
        return std::move(args);}
};

template<class... Ts>
pack_args(Ts&&...) -> pack_args<Ts...>;

Puis

std::apply(foo, pack_args{0, 1}.get());

Comme l'illustre ce lien godbolt : https://godbolt.org/z/dTejr9336

On a ainsi ordonné les arguments d'appel sans perdre le contrôle fin du passage d'argument que permet C++.

Et voilà, une petite sucrerie de C++, probablement rien de nouveau sous le soleil pour les experts, mais ça m'a beaucoup amusé de jouer avec ça hier soir !

Et accessoirement, ça résout un bug dans Pythran, mais ça, c'est juste pour mon plaisir personnel.

  • # Intéressant !

    Posté par  . Évalué à 1 (+1/-0).

    Après un paquet d'années à faire du C++ j'étais completement passé à côté de ça… Une idée de pourquoi cet ordonnancement des arguments n'est pas garanti (au risque de causer des bugs touchy à analyser) ?

    • [^] # Re: Intéressant !

      Posté par  (site web personnel) . Évalué à 1 (+1/-0).

      Je dis ça sans certitude, mais potentiellement pour des cas d'optimisations qui deviendraient impossibles si l'ordre d'évaluation était imposé.

      int f(int a, int b) {
          return b ? a : 0;
      }
      
      void main() {
          f(complex_computation(), 0);
      }

      On pourrait peut-être imaginer (si f peut-être transformée en fonction inline — possible qu'une certaine syntaxe soit nécessaire pour ça, je ne sais plus bien) que l'appel à complex_computation soit évité puisque son résultat serait ignoré dans tous les cas.

    • [^] # Re: Intéressant !

      Posté par  (site web personnel) . Évalué à 2 (+0/-0).

      Pour les performances évidemment. Si évaluer le second argument en premier permet de mieux paralléliser l'exécution (en cas de dépendance dans les données, d'utilisation de ports d'exécution limités, etc …)

      La majeure partie des "undefined behavior" existent pour permettre aux compilateurs d'optimiser le code.

  • # Fonctions pures

    Posté par  (site web personnel) . Évalué à 2 (+0/-0).

    La majeure partie du temps ce problème n'en est pas un dès lors que ça ne modifie pas un objet. Je me suis fait avoir une fois dans l'utilisation d'un va_arg en C il y a une dizaine d'années (sur le binding Lua-SDL2). Au moment d'analyser un format de chaine de caractère j'avais écrit :

    switch (*f) {
    case 'o':
        add(va_arg(struct foo), va_arg(size_t));
        break;
    [...]
    }

    Et effectivement, surtout dans ce cas précis c'est très embettant. Le C++ a par contre corrigé un ordre d'évaluation notamment sur des expressions qui ressemblent à foo = ++b || ++a mais honnêtement je trouve pas toujours ça élégant ou lisible.

    AI is a mental disorder

Envoyer un commentaire

Suivre le flux des commentaires

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