C++ et les Fold Expressions

Avant de lire ce texte, il serait sans doute préférable de vous familiariser au préalable avec les templates variadiques.

Notez que le texte est embryonnaire; j'enrichirai le tout quand j'aurai pu expérimenter moi-même avec ce mécanisme.

Avec C++ 17, C++ offre un nouveau (pour ce langage) mécanisme de manipulation d'expressions variadiques nommé Fold Expressions, par lequel le compilateur gagne une « compréhension » de l'expression en extension de certains opérateurs sur un Parameter Pack.

Par exemple, soit la fonction variadique moyenne() ci-dessous, légale avec C++ 14 :

template <class T>
   auto somme_elements(T && val) {
      return val;
   }
template <class T, class ... Args>
   auto somme_elements(T && val, Args &&... args) {
      return val + somme_elements(std::forward<Args>(args)...);
   }

template <class R, class ... Args>
   R moyenne(Args &&... args) {
      return static_cast<R>(somme_elements(std::forward<Args>(args)...)) / sizeof...(Args);
   }

Sans que cette fonction ne soit très complexe, elle demande une forme de pensée récursive et une construction manuelle de la somme d'une séquence variadique de valeurs, et demande de comprendre le relais parfait pour relayer les paramètres correctement à chaque appel de la chaîne. Comparez avec la fonction C++ 17 au comportement équivalent :

template <class ... Args>
   auto somme_elements(Args &&... args) {
      return args + ...;
   }

template <class R, class ... Args>
   R moyenne(Args &&... args) {
      return static_cast<R>(somme_elements(std::forward<Args>(args)...)) / sizeof...(Args);
    }

... ou encore, plus simplement (présumant que somme_elements() ne soit pas nécessaire pour d'autres fins) :

template <class R, class ... Args>
   R moyenne(Args &&... args) {
      return static_cast<R>(args + ...) / sizeof...(Args);
   }

... ce qui est remarquablement plus simple.

Les formats possibles sont :

Nom Expression pour un opérateur donné Interprétation Exemple (naïf)

Unary Right Fold

pack ...

template <class ... Ts>
   auto somme(Ts ... args) { return args + ...; }

Unary Left Fold

... pack

template <class ... Ts>
   auto somme(Ts ... args) { return ... + args; }

Binary Right Fold

pack ... init

template <class ... Ts>
   auto somme(Ts ... args) { return args + ... + 0; }

Binary Left Fold

init ... pack

template <class ... Ts>
   auto somme(Ts ... args) { return 0 + ... + args; }

Les Fold Expressions s'appliquent à la plupart des opérateurs, ce qui permet par exemple :

... d'appeler plusieurs fois la même fonction avec les éléments d'une séquence variadique (remarquez ici que nous exploitons l'opérateur ,)... 

// ...
template <class T, class ... Ts>
   auto ajout_un_a_un(Ts &&... elems) {
      vector<T> v;
      (v.push_back(elems), ...); // <--
      return v;
   }

... d'afficher plusieurs valeurs en séquence (sans espace entre eux dans l'exemple à droite), en utilisant ici l'opérateur << ...

template <class ... Args>
   ostream& projeter(ostream &os, Args &&... args) {
      return (os << ... << args) << endl;
   }

... de réaliser une conjonction logique de plusieurs prédicats, à l'aide de l'opéateur &&...

template <class T, class ... Pred>
   bool conjonction(T &&obj, Pred &&... preds) {
      return preds(obj) && ... && true;
   }

... générer une série de λ s'appliquant successivement à chaque élément d'une séquence variadique, etc.

template<class... T>
   void afficher_un_par_ligne(T... a) {
      ([&](auto &&ai) { cout << ai << endl; }(a),...);
   }

Un nouveau terrain de jeu pour les programmeuses et les programmeurs qui apprécient C++, en somme.

Lectures complémentaires

Quelques liens pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !