Ellipses et modèles variadiques

Cet article montre comment utiliser les points de suspension (...) avec des modèles variadiques C++. Les points de suspension ont eu de nombreuses utilisations en C et C++. Celles-ci incluent des listes d’arguments variables pour les fonctions. La printf() fonction de la bibliothèque runtime C est l’un des exemples les plus connus.

Un modèle variadicique est un modèle de classe ou de fonction qui prend en charge un nombre arbitraire d’arguments. Ce mécanisme est particulièrement utile pour les développeurs de bibliothèqueS C++ : vous pouvez l’appliquer à la fois aux modèles de classe et aux modèles de fonction, et ainsi fournir un large éventail de fonctionnalités et de flexibilité de type sécurisé et non trivial.

Syntaxe

Un point de suspension est utilisé de deux façons par des modèles variadiques. À gauche du nom du paramètre, il signifie un pack de paramètres et à droite du nom du paramètre, il développe les packs de paramètres en noms distincts.

Voici un exemple de base de la syntaxe de définition de modèle de classe variadic :

template<typename... Arguments> class classname;

Pour les packs de paramètres et les extensions, vous pouvez ajouter des espaces blancs autour des points de suspension, en fonction de vos préférences, comme illustré dans cet exemple :

template<typename ...Arguments> class classname;

Ou cet exemple :

template<typename ... Arguments> class classname;

Cet article utilise la convention indiquée dans le premier exemple (les points de suspension sont attachés à typename).

Dans les exemples précédents, Arguments il s’agit d’un pack de paramètres. La classe classname peut accepter un nombre variable d’arguments, comme dans les exemples suivants :

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;

En utilisant une définition de modèle de classe variadicique, vous pouvez également exiger au moins un paramètre :

template <typename First, typename... Rest> class classname;

Voici un exemple de base de la syntaxe de modèle de fonction variadicique :

template <typename... Arguments> returntype functionname(Arguments... args);

Le Arguments pack de paramètres est ensuite développé pour une utilisation, comme indiqué dans la section suivante.

D’autres formes de syntaxe de modèle de fonction variadicique sont possibles, notamment, mais pas limitées à ces exemples :

template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);

Les spécificateurs comme ceux-ci const sont également autorisés :

template <typename... Arguments> returntype functionname(const Arguments&... args);

Comme avec les définitions de classes de modèle variadic, vous pouvez rendre les fonctions qui nécessitent au moins un paramètre :

template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);

Les modèles variadiciques utilisent l’opérateur sizeof...() (non lié à l’opérateur plus ancien sizeof() ) :

template<typename... Arguments>
void tfunc(const Arguments&... args)
{
    constexpr auto numargs{ sizeof...(Arguments) };

    X xobj[numargs]; // array of some previously defined type X

    helper_func(xobj, args...);
}

En savoir plus sur le positionnement des points de suspension

Auparavant, cet article décrit le placement des points de suspension qui définit les packs de paramètres et les extensions sous cette forme : « à gauche du nom du paramètre, il signifie un pack de paramètres et à droite du nom du paramètre, il développe les packs de paramètres en noms distincts ». Bien que techniquement vrai, il peut être déroutant dans la traduction vers le code. Considérez les aspects suivants :

  • Dans une liste de paramètres de modèle (template <parameter-list>), typename... introduit un pack de paramètres de modèle.

  • Dans une clause parameter-declaration-clause (func(parameter-list)), un point de suspension « de niveau supérieur » introduit un pack de paramètres de fonction, et le positionnement des points de suspension est important :

    // v1 is NOT a function parameter pack:
    template <typename... Types> void func1(std::vector<Types...> v1);
    
    // v2 IS a function parameter pack:
    template <typename... Types> void func2(std::vector<Types>... v2);
    
  • Où les points de suspension s’affichent immédiatement après un nom de paramètre, vous disposez d’une extension de pack de paramètres.

Exemple

Un bon moyen d’illustrer le mécanisme de modèle de fonction variadic est de l’utiliser dans une réécriture de certaines des fonctionnalités de printf:

#include <iostream>

using namespace std;

void print() {
    cout << endl;
}

template <typename T> void print(const T& t) {
    cout << t << endl;
}

template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
    cout << first << ", ";
    print(rest...); // recursive call using pack expansion syntax
}

int main()
{
    print(); // calls first overload, outputting only a newline
    print(1); // calls second overload

    // these call the third overload, the variadic template,
    // which uses recursion as needed.
    print(10, 20);
    print(100, 200, 300);
    print("first", 2, "third", 3.14159);
}

Sortie

1
10, 20
100, 200, 300
first, 2, third, 3.14159

Remarque

La plupart des implémentations qui incorporent des modèles de fonction variadiciques utilisent la récursivité d’une certaine forme, mais il est légèrement différent de la récursivité traditionnelle. La récursivité traditionnelle implique une fonction qui s’appelle elle-même à l’aide de la même signature. (Il peut être surchargé ou modèle, mais la même signature est choisie chaque fois.) La récursivité variadicique implique l’appel d’un modèle de fonction variadicique à l’aide de nombres différents (presque toujours décroissants) d’arguments, et par conséquent, d’horodatage d’une signature différente à chaque fois. Un « cas de base » est toujours nécessaire, mais la nature de la récursivité est différente.