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 vulcainman . Évalué à 2 (+2/-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 entwanne (site web personnel) . Évalué à 8 (+8/-0).
Je dis ça sans certitude, mais potentiellement pour des cas d'optimisations qui deviendraient impossibles si l'ordre d'évaluation était imposé.
On pourrait peut-être imaginer (si
fpeut-ê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_computationsoit évité puisque son résultat serait ignoré dans tous les cas.[^] # Re: Intéressant !
Posté par Colin Pitrat (site web personnel) . Évalué à 10 (+9/-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.
[^] # Re: Intéressant !
Posté par pulkomandy (site web personnel, Mastodon) . Évalué à 10 (+11/-0).
Historiquement, le C (et le C++ en a hérité) spécifie très peu de choses, le moins possible. À chaque compilateur de choisir comment il implémente le standard.
Dans le cas présenté ici, pour passer des arguments à une fonction, le compilateur va en général les pousser sur la pile avant d'appeler la fonction.
Généralement en C cela se fait de la droite vers la gauche, car c'est la solution la plus simple pour gérer les fonctions à nombre d'arguments variables. La fonction appelée peut accéder à arguments[0] pour le premier argument, arguments[-1] pour le second, etc. Cela étant situé sur la pile, les index positifs sont réservés pour le contexte de la fonction elle-même (variables locales, préparation de l'appel d'autres fonctions, …). Il faut donc commencer par pousser le dernier argument, puis l'avant dernier, puis… jusqu'au premier.
Ainsi la fonction appelée a besoin d'un seul pointeur sur la pile pour accéder à la fois à ses variables locales et à ses arguments.
Cependant, il existe des architectures ou des compilateurs pour lesquels tous les arguments ne sont pas passés sur la pile. Par exemple, les 2 ou 4 premiers (en partant de la gauche cette fois-ci, vous suivez?) arguments peuvent être passés dans des registres.
Il peut arriver qu'un compilateur décide de préparer d'abord ces 2 ou 4 premiers arguments avant de pousser les autres.
Il peut aussi arriver qu'un compilateur soit obligé de traiter les arguments de gauche à droite, par exemple pour assurer la compatiblité avec une convention d'appel d'un autre langage utilisée par le système d'exploitation. C'est typiquement le cas pour Windows, où les APIs utilisent plutôt la convention d'appel de Pascal. Voire, encore pire, sur Amiga, ou des extensions au langage C permettent de spécifier manuellement quels arguments doivent être passés dans quels registres.
Le langage pourrait imposer un ordre d'évaluation, mais dans ce cas, certains compilateurs devraient évaluer les arguments puis ensuite réordonner les résultats pour préparer l'appel de la fonction.
La façon dont les arguments sont disposés en mémoire ou dans les registres peut être définie par une documentation d'ABI spécifique à un compilateur, une architecture ou un système d'exploitation. On pourra indirectement en déduire l'ordre probable d'évaluation des arguments, mais sans garantie. Cela laisse un peu de liberté à l'optimiseur s'il décide de réordonner les choses.
[^] # Re: Intéressant !
Posté par vulcainman . Évalué à 3 (+3/-0).
Merci pour l'explication, c'est limpide !
# Fonctions pures
Posté par David Demelier (site web personnel) . Évalué à 4 (+2/-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_argen 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 :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 || ++amais honnêtement je trouve pas toujours ça élégant ou lisible.AI is a mental disorder
# Compilation warning important
Posté par moi1392 . Évalué à 2 (+1/-1). Dernière modification le 12 octobre 2026 à 17:46.
Au passage, ce code :
int dump(int i) { std::cout << i << std::endl; }
devrait te donner un warning à la compilation avec un -wall (peut-être même un niveau de warning plus faible), la fonction est censée retourner un int mais ne retourne jamais rien.
Perso je passe ce warning en erreur sur mes gros projets, j'ai déjà eu du code qui crash quand il est compilé en release à cause de ça.
À partir du moment où le compilateur peut faire un code qui crash en optimisé, c'est un peu light je trouve de n'avoir qu'un warning.
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.