web-dev-qa-db-fra.com

Comment puis-je itérer sur une liste d'arguments de modèles variadic compactés?

J'essaie de trouver une méthode pour effectuer une itération sur une liste d'arguments de modèles variadiques de pack . Maintenant, comme pour toutes les itérations, vous avez besoin d'une sorte de méthode permettant de savoir combien d'arguments figurent dans la liste emballée, et plus obtenir individuellement les données d'une liste d'arguments compactés.

L'idée générale est de parcourir la liste, de stocker toutes les données de type int dans un vecteur, de stocker toutes les données de type char * dans un vecteur et de stocker toutes les données de type float dans un vecteur. Au cours de ce processus, il doit également exister un vecteur séparé qui stocke les caractères individuels de l'ordre dans lequel les arguments sont entrés. Par exemple, lorsque vous Push_back (a_float), vous faites également un Push_back ('f') qui stocke simplement un caractère individuel pour connaître l'ordre des données. Je pourrais aussi utiliser un std :: string ici et simplement utiliser + =. Le vecteur n'a été utilisé qu'à titre d'exemple.

Maintenant, la manière dont la chose est conçue est que la fonction elle-même est construite en utilisant une macro, malgré les intentions perverses, il est nécessaire, car il s’agit d’une expérience. Il est donc littéralement impossible d'utiliser un appel récursif, car l'implémentation réelle qui hébergera tout cela sera étendue lors de la compilation; et vous ne pouvez pas recréer une macro.

Malgré toutes les tentatives possibles, je suis toujours incapable de trouver le moyen de le faire. Donc, au lieu de cela, j'utilise une méthode plus compliquée qui consiste à construire un type et à le transférer dans le modèle varadic, à le développer à l'intérieur d'un vecteur et à l'itérer simplement. Cependant, je ne veux pas avoir à appeler la fonction comme:

foo(arg(1), arg(2.0f), arg("three");

La vraie question est donc: comment puis-je m'en passer? Pour vous aider à mieux comprendre ce que le code est en train de faire, j'ai collé l'approche optimiste que j'utilise actuellement.

struct any {
  void do_i(int   e) { INT    = e; }
  void do_f(float e) { FLOAT  = e; }
  void do_s(char* e) { STRING = e; }

  int   INT;
  float FLOAT;
  char *STRING;
};


template<typename T> struct get        { T      operator()(const any& t) { return T();      } };
template<>           struct get<int>   { int    operator()(const any& t) { return t.INT;    } };
template<>           struct get<float> { float  operator()(const any& t) { return t.FLOAT;  } };
template<>           struct get<char*> { char*  operator()(const any& t) { return t.STRING; } };

#define def(name)                                  \
  template<typename... T>                          \
  auto name (T... argv) -> any {                   \
   std::initializer_list<any> argin = { argv... }; \
    std::vector<any> args = argin;
#define get(name,T)  get<T>()(args[name])
#define end }

any arg(int   a) { any arg; arg.INT    = a; return arg; }
any arg(float f) { any arg; arg.FLOAT  = f; return arg; }
any arg(char* s) { any arg; arg.STRING = s; return arg; }

Je sais que c'est méchant, mais c'est une pure expérience, et ne sera pas utilisé dans le code de production. C'est purement une idée. Cela pourrait probablement être fait d'une meilleure façon. Mais un exemple de la façon dont vous utiliseriez ce système:

def(foo)
  int data = get(0, int);
  std::cout << data << std::endl;
end

ressemble beaucoup à python. cela fonctionne aussi, mais le seul problème est comment vous appelez cette fonction . Voici un exemple rapide:

foo(arg(1000));

Je suis obligé de construire un nouveau type, très esthétique, mais cela ne veut pas dire que ces macros ne le sont pas non plus. Mis à part le point, je veux juste la possibilité de faire: foo (1000);

Je sais que cela peut être fait, j'ai juste besoin d'une méthode d'itération ou, plus important encore, d'une méthode std :: get pour les listes d'arguments de modèles variadiques compactés. Ce qui, j'en suis sûr, peut être fait.

De plus, je suis bien conscient du fait que ce n'est pas vraiment convivial, car je ne supporte que int, float, char * et c'est bien avec moi. Je n’exige rien d’autre, et je vais ajouter des vérifications pour utiliser type_traits afin de valider que les arguments passés sont bien les arguments corrects pour produire une erreur de compilation si les données sont incorrectes. Ce n'est purement pas un problème. Je n'ai également besoin d'aucun soutien pour ces types de POD.

Ce serait très apprécié si je pouvais obtenir une aide constructive, par opposition à des arguments concernant mon utilisation purement illogique et stupide de macros et de types POD uniquement. Je sais très bien à quel point le code est fragile et cassé. C’est une expérience merley, et je pourrai ultérieurement corriger les problèmes liés aux données autres que POD et les rendre plus sûrs en termes de types et utilisables.

Merci pour votre engagement et j'ai hâte de vous aider.

41
graphitemaster

Si vous souhaitez incorporer des arguments à any, vous pouvez utiliser la configuration suivante. J'ai également rendu la classe any un peu plus utilisable, même si ce n'est techniquement pas une classe any.

#include <vector>
#include <iostream>

struct any {
  enum type {Int, Float, String};
  any(int   e) { m_data.INT    = e; m_type = Int;}
  any(float e) { m_data.FLOAT  = e; m_type = Float;}
  any(char* e) { m_data.STRING = e; m_type = String;}
  type get_type() const { return m_type; }
  int get_int() const { return m_data.INT; }
  float get_float() const { return m_data.FLOAT; }
  char* get_string() const { return m_data.STRING; }
private:
  type m_type;
  union {
    int   INT;
    float FLOAT;
    char *STRING;
  } m_data;
};

template <class ...Args>
void foo_imp(const Args&... args)
{
    std::vector<any> vec = {args...};
    for (unsigned i = 0; i < vec.size(); ++i) {
        switch (vec[i].get_type()) {
            case any::Int: std::cout << vec[i].get_int() << '\n'; break;
            case any::Float: std::cout << vec[i].get_float() << '\n'; break;
            case any::String: std::cout << vec[i].get_string() << '\n'; break;
        }
    }
}

template <class ...Args>
void foo(Args... args)
{
    foo_imp(any(args)...);  //pass each arg to any constructor, and call foo_imp with resulting any objects
}

int main()
{
    char s[] = "Hello";
    foo(1, 3.4f, s);
}

Il est cependant possible d'écrire des fonctions pour accéder au nième argument dans une fonction de modèle variadique et d'appliquer une fonction à chaque argument, ce qui pourrait être un meilleur moyen de faire tout ce que vous voulez réaliser.

28
UncleBens

Ce n’est pas comme cela que l’on utiliserait typiquement les modèles Variadic, pas du tout.

Les itérations sur un pack variadique n'étant pas possibles, conformément aux règles de langue, vous devez vous tourner vers la récursivité.

class Stock
{
public:
  bool isInt(size_t i) { return _indexes.at(i).first == Int; }
  int getInt(size_t i) { assert(isInt(i)); return _ints.at(_indexes.at(i).second); }

  // Push (a)
  template <typename... Args>
  void Push(int i, Args... args) {
    _indexes.Push_back(std::make_pair(Int, _ints.size()));
    _ints.Push_back(i);
    this->Push(args...);
  }

  // Push (b)
  template <typename... Args>
  void Push(float f, Args... args) {
    _indexes.Push_back(std::make_pair(Float, _floats.size()));
    _floats.Push_back(f);
    this->Push(args...);
  }

private:
  // Push (c)
  void Push() {}

  enum Type { Int, Float; };
  typedef size_t Index;

  std::vector<std::pair<Type,Index>> _indexes;
  std::vector<int> _ints;
  std::vector<float> _floats;
};

Exemple (en action), supposons que nous ayons Stock stock;:

  • stock.Push(1, 3.2f, 4, 5, 4.2f); est résolu en (a) car le premier argument est un int
  • this->Push(args...) est étendu à this->Push(3.2f, 4, 5, 4.2f);, ce qui est résolu en (b) car le premier argument est un float
  • this->Push(args...) est étendu à this->Push(4, 5, 4.2f);, ce qui est résolu en (a) car le premier argument est un int
  • this->Push(args...) est étendu à this->Push(5, 4.2f);, ce qui est résolu en (a) car le premier argument est un int
  • this->Push(args...) est étendu à this->Push(4.2f);, ce qui est résolu en (b) car le premier argument est un float
  • this->Push(args...) est étendu à this->Push();, ce qui est résolu en (c) car il n'y a pas d'argument, mettant ainsi fin à la récursion

Ainsi:

  • Ajouter un autre type à gérer est aussi simple que d’ajouter une autre surcharge, en changeant le premier type (par exemple, std::string const&)
  • Si un type complètement différent est passé (par exemple, Foo), aucune surcharge ne peut être sélectionnée, ce qui entraîne une erreur lors de la compilation.

Une mise en garde: La conversion automatique signifie qu'une double sélectionnerait une surcharge (b) et une short sélectionnerait une surcharge (a). Si cela n'est pas souhaité, il faut alors introduire SFINAE, ce qui rend la méthode légèrement plus compliquée (enfin, leurs signatures au moins), exemple:

template <typename T, typename... Args>
typename std::enable_if<is_int<T>::value>::type Push(T i, Args... args);

is_int serait quelque chose comme:

template <typename T> struct is_int { static bool constexpr value = false; };
template <> struct is_int<int> { static bool constexpr value = true; };

Une autre alternative, cependant, consisterait à envisager un type de variante. Par exemple:

typedef boost::variant<int, float, std::string> Variant;

Il existe déjà, avec tous les utilitaires, il peut être stocké dans une vector, copié, etc. ... et semble vraiment très bien ce dont vous avez besoin, même s'il n'utilise pas de modèles Variadic.

20
Matthieu M.

Vous pouvez en créer un conteneur en l'initialisant avec votre pack de paramètres entre {}. Tant que le type de paramètre ... est homogène ou au moins convertible en type d'élément de votre conteneur, cela fonctionnera. (testé avec g ++ 4.6.1)

#include <array>

template <class... Params>
void f(Params... params) {
    std::array<int, sizeof...(params)> list = {params...};
}
17
karel

Il n’existe pas de fonctionnalité spécifique pour le moment, mais vous pouvez utiliser certaines solutions de contournement.

Utiliser la liste d'initialisation

Une solution de contournement utilise le fait que les sous-expressions de listes d'initialisation sont évaluées dans l'ordre. int a[] = {get1(), get2()} exécutera get1 avant d'exécuter get2. Peut-être que les expressions de plis seront utiles pour des techniques similaires à l’avenir. Pour appeler do() sur chaque argument, vous pouvez faire quelque chose comme ceci:

template <class... Args>
void doSomething(Args... args) {
    int x[] = {args.do()...};
}

Toutefois, cela ne fonctionnera que lorsque do() renverra une int. Vous pouvez utiliser l'opérateur virgule pour prendre en charge les opérations qui ne renvoient pas une valeur correcte.

template <class... Args>
void doSomething(Args... args) {
    int x[] = {(args.do(), 0)...};
}

Pour faire des choses plus complexes, vous pouvez les mettre dans une autre fonction:

template <class Arg>
void process(Arg arg, int &someOtherData) {
    // You can do something with arg here.
}

template <class... Args>
void doSomething(Args... args) {
    int someOtherData;
    int x[] = {(process(args, someOtherData), 0)...};
}

Notez qu'avec générique lambdas (C++ 14), vous pouvez définir une fonction permettant de faire cette passe-partout pour vous.

template <class F, class... Args>
void do_for(F f, Args... args) {
    int x[] = {(f(args), 0)...};
}

template <class... Args>
void doSomething(Args... args) {
    do_for([&](auto arg) {
        // You can do something with arg here.
    }, args...);
}

Utiliser la récursion

Une autre possibilité consiste à utiliser la récursivité. Voici un petit exemple qui définit une fonction similaire do_for comme ci-dessus.

template <class F, class First, class... Rest>
void do_for(F f, First first, Rest... rest) {
    f(first);
    do_for(f, rest...);
}
template <class F>
void do_for(F f) {
    // Parameter pack is empty.
}

template <class... Args>
void doSomething(Args... args) {
    do_for([&](auto arg) {
        // You can do something with arg here.
    }, args...);
}
8
JojOatXGME

Les gammes basées sur les boucles sont merveilleuses:

#include <iostream>
#include <any>

template <typename... Things>
void printVariadic(Things... things) {
    for(const auto p : {things...}) {
        std::cout << p.type().name() << std::endl;
    }
}

int main() {
    printVariadic(std::any(42), std::any('?'), std::any("C++"));
}

Pour moi, this produit la sortie:

i
c
PKc

Here est un exemple sans std::any, qui pourrait être plus facile à comprendre pour ceux qui ne connaissent pas std::type_info:

#include <iostream>

template <typename... Things>
void printVariadic(Things... things) {
    for(const auto p : {things...}) {
        std::cout << p << std::endl;
    }
}

int main() {
    printVariadic(1, 2, 3);
}

Comme vous vous en doutez, cela produit:

1
2
3
4
OMGtechy

Vous ne pouvez pas parcourir, mais vous pouvez parcourir la liste. Consultez l'exemple printf () sur wikipedia: http://en.wikipedia.org/wiki/C++0x#Variadic_templates

3
chmeee

Vous pouvez utiliser plusieurs modèles variadiques, c’est un peu désordonné, mais cela fonctionne et est facile à comprendre . Vous avez simplement une fonction avec le modèle variadique comme ceci:

template <typename ...ArgsType >
void function(ArgsType... Args){
     helperFunction(Args...);
}

Et une fonction d'assistance comme celle-ci:

void helperFunction() {}

template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {

     //do what you want with t
    function(Args...);

}

Maintenant, lorsque vous appelez "fonction", la "fonction d'assistance" sera appelée et isolera le premier paramètre passé du reste, cette variable peut être utilisée pour appeler une autre fonction (ou autre chose). Ensuite, "fonction" sera appelée encore et encore jusqu'à ce qu'il ne reste plus de variables. Notez que vous devrez peut-être déclarer helperClass avant "function".

Le code final ressemblera à ceci:

void helperFunction();

template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args);

template <typename ...ArgsType >
void function(ArgsType... Args){
     helperFunction(Args...);
}

void helperFunction() {}

template <typename T, typename ...ArgsType >
void helperFunction(T t, ArgsType... Args) {

     //do what you want with t
    function(Args...);

}

Le code n'est pas testé.

0
user3911448