Parcourir les arguments d'une fonction variadique

Article suivant: Valeur, référence ou pointeur ? (1/2)
Article précédent: Tableau dans un std::vector

À l’approche de Noël et du déballage de cadeaux, faisons un tour sur le déballage des paramètres variadiques.

Info : Ce qui suit n’est plus d’actualité depuis C++17 et les fold expressions.

Fonction récursive

La méthode habituelle pour utiliser chaque paramètre est la récursion jusqu’à plus d’argument ou jusqu’à un nombre défini, généralement 1.

Quelque chose dans ce goût-là:

#include <iostream>

void f1()
{}

template<class T, class... Args>
void f1(const T& first, const Args&... others)
{
  std::cout << first << '\n';
  f1(others...);
}

template<class T>
void f2(const T& first)
{
  std::cout << first << '\n';
}

template<class T, class... Args>
void f2(const T& first, const Args&... others)
{
  std::cout << first << ", ";
  f2(others...);
}

int main()
{
  f1(1, 2.4, "plop");
  f2(1, 2.4, "plop");
}

Ce qui affiche:

1
2.4
plop
1, 2.4, plop

Sans récursivité

Il existe cependant une autre façon de faire qui n’utilise pas la récursivité. J’ai découvert cette méthode sur le forum de openclassroms (ce sujet, 10ème message), elle est très astucieuse.

Implémentée sous la forme d’une macro cela donne:

#define UNPACK(...)                   \
  (void)::std::initializer_list<int>{ \
    (void((__VA_ARGS__)), 0)...       \
  }
  • Le premier void permet d’ignorer l’initializer_list créé.
  • (void((__VA_ARGS__)), 0)... évalue l’ensemble de l’expression, ignore le résulat (grâce au void) et retourne un caractère dans l’initalizer_list.
  • Au final, l’expression est dépaquetée et l’initializer_list est rempli de 0.

Avec cette macro les 2 fonctions précédentes deviennent:

template<class... Args>
void f1(const Args&... args)
{
  UNPACK(std::cout << args << '\n');
}

template<class T, class... Args>
void f2(const T& first, const Args&... others)
{
  std::cout << first;
  UNPACK(std::cout << ", " << others);
  std::cout << '\n';
}

Ce qui simplifie considérablement l’écriture :).

Évidemment, cette macro se trouve depuis dans falcon: falcon.cxx.

Fold expression et C++17

Depuis C++17, le langage introduit les fold expressions qui ne font pas moins que la macro précédente. Elles font même un peu plus, mais je vous laisse consulter la doc pour en prendre connaissance.

template<class... Args>
void f1(const Args&... args)
{
  ((std::cout << args << '\n'), ...);
}

template<class T, class... Args>
void f2(const T& first, const Args&... others)
{
  std::cout << first;
  ((std::cout << ", " << others), ...);
  std::cout << '\n';
}

La structure du code est très proche du précédent, il n’y a que la macro qui est remplacée par une paire de parenthèse et , ....

Par contre, il ne faut pas oublier que pour certain type, l’opérateur , peut être surchargé et engendrer des comportements indésirables. La solution consiste – comme précédemment – à entourer l’expression de void. Comme cela devient un peu moche, on recycle la macro UNPACK:

#define UNPACK(...) (void((__VA_ARGS__)), ...)

Commentaires

Aucun commentaire pour le moment :'(

Le système de commentaire passe par les issues de github et aucun n'est associée au billet. Vous pouvez faire votre commentaire dans une issue qui a comme titre celui du billet. Je me chargerai de les associer.

Revenir en haut