web-dev-qa-db-fra.com

Comment appeler une fonction sur tous les arguments de modèle variadic?

Je voudrais faire

template<typename... ArgTypes> void print(ArgTypes... Args)
{
   print(Args)...;
}

Et que ce soit l'équivalent de cette chaîne récursive assez volumineuse:

template<typename T, typename... ArgTypes> void print(const T& t, ArgTypes... Args)
{
  print(t);
  print(Args...);
}

suivi de spécialisations explicites à un seul paramètre pour chaque type que je souhaite imprimer.

Le "problème" avec l'implémentation récursive est que beaucoup de code redondant est généré, car chaque étape récursive aboutit à une nouvelle fonction de N-1 arguments, alors que le code que j'aimerais avoir ne générerait du code que pour une seule fonction N- arg print, et aurait au plus N spécialisé print fonctions.

44
rubenvb

expression C++ 17 fois

(f(args), ...);

Si vous appelez quelque chose qui pourrait renvoyer un objet avec un opérateur de virgule surchargé, utilisez:

((void)f(args), ...);

solution pré-C++ 17

L'approche typique ici est d'utiliser un initialiseur de liste stupide et de faire l'expansion à l'intérieur:

{ print(Args)... }

L'ordre d'évaluation est garanti de gauche à droite dans les initialiseurs bouclés.

Mais print renvoie void, nous devons donc contourner ce problème. Faisons alors un int.

{ (print(Args), 0)... }

Cela ne fonctionnera pas directement comme une déclaration. Nous devons lui donner un type.

using expand_type = int[];
expand_type{ (print(Args), 0)... };

Cela fonctionne tant qu'il y a toujours un élément dans le pack Args. Les tableaux de taille zéro ne sont pas valides, mais nous pouvons contourner ce problème en leur faisant toujours avoir au moins un élément.

expand_type{ 0, (print(Args), 0)... };

Nous pouvons rendre ce modèle réutilisable avec une macro.

namespace so {
    using expand_type = int[];
}

#define SO_EXPAND_SIDE_EFFECTS(PATTERN) ::so::expand_type{ 0, ((PATTERN), 0)... }

// usage
SO_EXPAND_SIDE_EFFECTS(print(Args));

Cependant, rendre ce réutilisable nécessite un peu plus d'attention à certains détails. Nous ne voulons pas que des opérateurs de virgule surchargés soient utilisés ici. La virgule ne peut pas être surchargée avec l'un des arguments void, alors profitons-en.

#define SO_EXPAND_SIDE_EFFECTS(PATTERN) \
        ::so::expand_type{ 0, ((PATTERN), void(), 0)... }

Si vous êtes paranoïaque craignant que le compilateur n'alloue de grands tableaux de zéros pour rien, vous pouvez utiliser un autre type qui peut être initialisé en liste comme ça mais ne stocke rien.

namespace so {
    struct expand_type {
        template <typename... T>
        expand_type(T&&...) {}
    };
}
69

Expression C++ 17 fois:

(f(args), ...);

Gardez les choses simples simples ;-)

Si vous appelez quelque chose qui pourrait renvoyer un objet avec un opérateur de virgule surchargé, utilisez:

((void)f(args), ...);
16
Benjamin Buch

Vous pouvez utiliser une approche encore plus simple et lisible

template<typename... ArgTypes> void print(ArgTypes... Args)
{
   for (const auto& arg : {Args...})
   {
      print(arg);
   }
}

J'ai joué avec les deux variantes sur compile Explorer et gcc et clang avec O3 ou O2 produisent exactement le même code mais ma variante est évidemment plus propre.

7
Anton Dyachenko