web-dev-qa-db-fra.com

Extension du pack de modèles variadic

J'essaie d'apprendre des modèles et des fonctions variadiques. Je ne comprends pas pourquoi ce code ne compile pas:

template<typename T>
static void bar(T t) {}

template<typename... Args>
static void foo2(Args... args)
{
    (bar(args)...);
}

int main()
{
    foo2(1, 2, 3, "3");
    return 0;    
}

Quand je compile ça échoue avec l'erreur:

Erreur C3520: 'args': le pack de paramètres doit être développé dans ce contexte

(dans la fonction foo2).

66
Viacheslav Dronov

L'un des endroits où une extension de pack peut se produire est à l'intérieur de braced-init-list. Vous pouvez en tirer parti en plaçant l'extension dans la liste d'initialisation d'un tableau factice:

template<typename... Args>
static void foo2(Args &&... args)
{
    int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
}

Pour expliquer le contenu de l’initialiseur plus en détail:

{ 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
  |       |       |                        |     |
  |       |       |                        |     --- pack expand the whole thing 
  |       |       |                        |   
  |       |       --perfect forwarding     --- comma operator
  |       |
  |       -- cast to void to ensure that regardless of bar()'s return type
  |          the built-in comma operator is used rather than an overloaded one
  |
  ---ensure that the array has at least one element so that we don't try to make an
     illegal 0-length array when args is empty

Démo .

L’expansion dans {} présente un avantage important: elle garantit une évaluation de gauche à droite.


Avec C++ 1z expressions de plis , vous pouvez simplement écrire

((void) bar(std::forward<Args>(args)), ...);
102
T.C.

Les packs de paramètres ne peuvent être développés que dans une liste de contextes strictement définie, et l'opérateur , n'en fait pas partie. En d'autres termes, il n'est pas possible d'utiliser l'extension de pack pour générer une expression constituée d'une série de sous-expressions délimitées par l'opérateur ,.

La règle générale est "L'extension peut générer un liste de ,- modèles séparés par , est un délimiteur liste". L'opérateur , ne construit pas de liste au sens de la grammaire.

Pour appeler une fonction pour chaque argument, vous pouvez utiliser la récursivité (qui est l'outil principal dans la boîte du programmeur de modèles variadiques):

template <typename T>
void bar(T t) {}

void foo2() {}

template <typename Car, typename... Cdr>
void foo2(Car car, Cdr... cdr)
{
  bar(car);
  foo2(cdr...);
}

int main()
{
  foo2 (1, 2, 3, "3");
}

Exemple live

37
Angew

COPIE SANS CHEVEUX [approuvé par sa source]

Les packs de paramètres ne peuvent être développés que dans une liste de contextes strictement définie, et l'opérateur , n'en fait pas partie. En d'autres termes, il n'est pas possible d'utiliser l'extension de pack pour générer une expression constituée d'une série de sous-expressions délimitées par l'opérateur ,.

La règle générale est "L'extension peut générer une liste de ,- modèles séparés, où , est un délimiteur de liste." L'opérateur , ne construit pas de liste au sens de la grammaire.

Pour appeler une fonction pour chaque argument, vous pouvez utiliser la récursivité (qui est l'outil principal dans la boîte du programmeur de modèles variadiques):

#include <utility>

template<typename T>
void foo(T &&t){}

template<typename Arg0, typename Arg1, typename ... Args>
void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
    foo(std::forward<Arg0>(arg0));
    foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}

auto main() -> int{
    foo(1, 2, 3, "3");
}

INFORMATIONS UTILES NON COPIÉES 

Une autre chose que vous n'avez probablement pas vue dans cette réponse est l'utilisation du spécificateur && et du std::forward. En C++, le spécificateur && peut signifier l'une des deux choses suivantes: rvalue-references ou références universelles.

Je n'entrerai pas dans les références rvalue, mais à quelqu'un travaillant avec des modèles variadiques; les références universelles sont un don de Dieu.

Perfect Forwarding

Une des utilisations de std::forward et des références universelles est la transmission parfaite des types à d’autres fonctions.

Dans votre exemple, si nous passons un int& à foo2, il sera automatiquement rétrogradé à int en raison de la signature de la fonction foo2 générée après la déduction du modèle. Si vous souhaitez ensuite transférer cette arg à une autre fonction qui le modifierait par référence, obtiendra des résultats non souhaités (la variable ne sera pas modifiée) car foo2 transmettra une référence au temporaire créé en lui transmettant une variable int. Pour contourner ce problème, nous spécifions une fonction de transfert permettant de convertir tout type de reference en variable (rvalue ou lvalue). Ensuite, pour être sûr de passer le type exact passé dans la fonction de transfert, nous utilisons std::forward, then et only, puis nous autorisons le rétrogradation des types; parce que nous sommes maintenant au point où cela compte le plus.

Si vous en avez besoin, lisez davantage sur références universelles et transmission parfaite ; Scott Meyers est une excellente ressource.

15
CoffeeandCode

Vous pouvez utiliser make_Tuple pour l'expansion du pack car il introduit un contexte dans lequel la séquence , produite par une expansion est valide.

make_Tuple( (bar(std::forward<Args>(args)), 0)... );

Maintenant, je soupçonne que le tuple de zéros inutilisé/non nommé/temporaire produit est détectable par le compilateur et optimisé.

Démo

2
Lorah Attkins

La solution C++ 17 est très proche de votre code attendu:

template<typename T>
static void bar(T t) {}

template<typename... Args>
static void foo2(Args... args) {
    (bar(args), ...);
}

int main() {
    foo2(1, 2, 3, "3");
    return 0;    
}

Cela élargit le motif avec l'opérateur virgule entre chaque expression

// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));
1
Guillaume Racicot

Ceci est un exemple complet, basé sur les réponses fournies ici.

Exemple pour reproduire console.log tel qu’il apparaît en JavaScript:

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

Nom de fichier par exemple js_console.h:

#include <iostream>
#include <utility>

class Console {
protected:
    template <typename T>
    void log_argument(T t) {
        std::cout << t << " ";
    }
public:
    template <typename... Args>
    void log(Args&&... args) {
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void warn(Args&&... args) {
        cout << "WARNING: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void error(Args&&... args) {
        cout << "ERROR: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }
};
0
lama12345