web-dev-qa-db-fra.com

Génération d'un membre de classe par argument de modèle variadic

J'ai une classe de modèle où chaque argument de modèle représente un type de valeur que le calcul interne peut gérer. Des modèles (au lieu de surcharger les fonctions) sont nécessaires car les valeurs sont passées comme boost :: any et leurs types ne sont pas clairement définis avant l'exécution. 

Pour correctement transtyper vers les types corrects, j'aimerais avoir une liste de membres pour chaque type d'argument variadique, quelque chose comme ceci:

template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
    std::vector<T1> m_argumentsOfType1;
    std::vector<T2> m_argumentsOfType2; // ...
};

Ou bien, j'aimerais stocker les types d'argument de modèle dans une liste, afin de faire de la magie RTTI avec elle (?). Mais comment les sauvegarder dans un membre std :: initializer_list ne me semble pas non plus clair.

Merci pour toute aide!

25
user1101674

Comme vous l'avez déjà indiqué, le meilleur moyen consiste à utiliser un tuple:

template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
    std::Tuple<std::vector<AcceptedTypes>...> vectors;
};

C'est le seul moyen de multiplier les "champs" car vous ne pouvez pas le faire magiquement pour épeler les noms de champs. Une autre chose importante est peut-être d’obtenir un accès nommé à eux. Je suppose que ce que vous essayez d’obtenir, c’est d’avoir plusieurs vecteurs avec unique types, afin que vous puissiez avoir la possibilité suivante de "rechercher" le bon vecteur par son type de valeur:

template <class T1, class T2>
struct SameType
{
    static const bool value = false;
};

template<class T>
struct SameType<T, T>
{
    static const bool value = true;
};

template <typename... Types>
class MyClass
{
     public:
     typedef std::Tuple<vector<Types>...> vtype;
     vtype vectors;

     template<int N, typename T>
     struct VectorOfType: SameType<T,
        typename std::Tuple_element<N, vtype>::type::value_type>
     { };

     template <int N, class T, class Tuple,
              bool Match = false> // this =false is only for clarity
     struct MatchingField
     {
         static vector<T>& get(Tuple& tp)
         {
             // The "non-matching" version
             return MatchingField<N+1, T, Tuple,
                    VectorOfType<N+1, T>::value>::get(tp);
         }
     };

     template <int N, class T, class Tuple>
     struct MatchingField<N, T, Tuple, true>
     {
        static vector<T>& get(Tuple& tp)
        {
            return std::get<N>(tp);
        }
     };

     template <typename T>
     vector<T>& access()
     {
         return MatchingField<0, T, vtype,
                VectorOfType<0, T>::value>::get(vectors);
     }
};

Voici le test afin que vous puissiez l'essayer:

int main( int argc, char** argv )
{
    int twelf = 12.5;
    typedef reference_wrapper<int> rint;

    MyClass<float, rint> mc;
    vector<rint>& i = mc.access<rint>();

    i.Push_back(twelf);

    mc.access<float>().Push_back(10.5);

    cout << "Test:\n";
    cout << "floats: " << mc.access<float>()[0] << endl;
    cout << "ints: " << mc.access<rint>()[0] << endl;
    //mc.access<double>();

    return 0;
}

Si vous utilisez un type qui ne figure pas dans la liste des types que vous avez passés pour spécialiser MyClass (voir cet accès commenté pour un double), vous obtiendrez une erreur de compilation, pas trop lisible, mais gcc indiquera au moins le bon endroit a causé le problème et au moins un tel message d'erreur suggère la cause correcte du problème - ici, par exemple, si vous essayez de faire mc.access <double> ():

 error: ‘value’ is not a member of ‘MyClass<float, int>::VectorOfType<2, double>’
16
Ethouris

Une façon de faire une telle chose, comme mentionné dans le commentaire de πάντα-ῥεῖ est d'utiliser un Tuple. Ce qu'il n'a pas expliqué (probablement pour vous sauver de vous-même), c'est à quoi cela pourrait ressembler.

Voici un exemple:

using namespace std;

// define the abomination    
template<typename...Types>
struct thing
{
    thing(std::vector<Types>... args)
    : _x { std::move(args)... }
    {}

    void print()
    {
        do_print_vectors(std::index_sequence_for<Types...>());
    }

private:
    template<std::size_t... Is>
    void do_print_vectors(std::index_sequence<Is...>)
    {
        using swallow = int[];
        (void)swallow{0, (print_one(std::get<Is>(_x)), 0)...};
    }

    template<class Vector>
    void print_one(const Vector& v)
    {
        copy(begin(v), end(v), ostream_iterator<typename Vector::value_type>(cout, ","));
        cout << endl;
    }

private:
    Tuple<std::vector<Types>...> _x;
};


// test it
BOOST_AUTO_TEST_CASE(play_tuples)
{
    thing<int, double, string> t {
        { 1, 2, 3, },
        { 1.1, 2.2, 3.3 },
        { "one"s, "two"s, "three"s }
    };

    t.print();
}

production attendue:

1,2,3,
1.1,2.2,3.3,
one,two,three,
5
Richard Hodges

Une autre solution qui n'utilise pas de nuplets consiste à utiliser CRTP pour créer une hiérarchie de classes où chaque classe de base est une spécialisation pour l'un des types suivants:

#include <iostream>
#include <string>

template<class L, class... R> class My_class;

template<class L>
class My_class<L>
{
public:

protected:
  L get()
  {
    return val;
  }

  void set(const L new_val)
  {
    val = new_val;
  }

private:
  L val;
};

template<class L, class... R>
class My_class : public My_class<L>, public My_class<R...>
{
public:
  template<class T>
  T Get()
  {
    return this->My_class<T>::get();
  }

  template<class T>
  void Set(const T new_val)
  {
    this->My_class<T>::set(new_val);
  }
};

int main(int, char**)
{
  My_class<int, double, std::string> c;
  c.Set<int>(4);
  c.Set<double>(12.5);
  c.Set<std::string>("Hello World");

  std::cout << "int: " << c.Get<int>() << "\n";
  std::cout << "double: " << c.Get<double>() << "\n";
  std::cout << "string: " << c.Get<std::string>() << std::endl;

  return 0;
}
2
shay

Voici une implémentation moins qu'efficace utilisant boost::variant:

template<typename ... Ts>
using variant_vector = boost::variant< std::vector<Ts>... >;

template<typename ...Ts>
struct MyClass {
  using var_vec = variant_vector<Ts...>;
  std::array<var_vec, sizeof...(Ts)> vecs;
};

nous créons une variante-vecteur pouvant contenir l'un d'une liste de types. Vous devez utiliser boost::variant pour accéder au contenu (ce qui signifie connaître le type du contenu ou écrire un visiteur).

Nous stockons ensuite un tableau de ces vecteurs variants, un par type.

Désormais, si votre classe ne contient qu'un seul type de données, vous pouvez supprimer le tableau et n’avoir qu’un seul membre de type var_vec.

Je ne vois pas pourquoi vous voudriez un vecteur de chaque type. Je pouvais voir vouloir un vecteur où chaque élément est de n'importe quel type. Ce serait un vector<variant<Ts...>>, par opposition au variant<vector<Ts>...> ci-dessus.

variant<Ts...> est la boost union-with-type. any est la boost smart -void*. optional est la boost il-ou-pas.

template<class...Ts>
boost::optional<boost::variant<Ts...>> to_variant( boost::any );

peut être une fonction utile, qui prend une any et tente de la convertir en l'un des types Ts... de la variant, et la retourne si elle réussit (et retourne une optional vide, sinon).

0