web-dev-qa-db-fra.com

vecteur tuple et initializer_list

J'ai essayé de compiler les extraits suivants avec gcc4.7

vector<pair<int,char> > vp = {{1,'a'},{2,'b'}};
//For pair vector, it works like a charm.

vector<Tuple<int,double,char> > vt = {{1,0.1,'a'},{2,4.2,'b'}};

Cependant, pour le vecteur de tuples, le compilateur se plaint:

erreur: la conversion en ‘std :: Tuple’ à partir de la liste des initialiseurs utiliserait le constructeur explicite ‘constexpr std :: Tuple <> :: Tuple (_UElements && ...) [with _UElements = {int, double, char}; = vide; _Elements = {int, double, char}] "

Les informations d'erreur déversées par le compilateur sont du charabia total pour moi, et je ne sais pas comment les constructeurs de Tuple ont été implémentés, mais je sais qu'ils sont tout à fait d'accord avec une initialisation uniforme (comme: Tuple<int,float,char>{1,2.2,'X'}), par conséquent, je me demande si le problème que j'ai rencontré n'est qu'un TODO du compilateur ou s'il est défini par la norme C++ 11.

27
Need4Steed

Le std::Tuple les constructeurs sont explicit. Cela signifie que ce que vous voulez faire n'est pas possible, car la syntaxe que vous souhaitez utiliser est définie en termes d'initialisation de la copie (ce qui interdit d'appeler un constructeur explicit). En revanche, std::Tuple<int, float, char> { 1, 2.2, 'X' } utilise l'initialisation directe. std::pair n'a que des constructeurs nonexplicit.

Utilisez soit l'initialisation directe, soit l'une des fonctions d'usine du tuple standard (par exemple std::make_Tuple).

11
Luc Danton

C'est en fait faisable, avec les fonctionnalités de c ++ 11.

Oui, la liste initializer_list veut que tous ses éléments soient du même type. L'astuce est que nous pouvons créer une classe wrapper qui peut être static_cast à tous les types que nous voulons. Ceci est facile à réaliser:

 template <typename... tlist>
 class MultiTypeWrapper {
 };

 template <typename H>
 class MultiTypeWrapper<H> {
 public:
   MultiTypeWrapper() {}

   MultiTypeWrapper(const H &value) : value_(value) {}

   operator H () const {
     return value_;
   }
 private:
   H value_;
 };

 template <typename H, typename... T>
 class MultiTypeWrapper<H, T...> 
   : public MultiTypeWrapper<T...> {

 public:
   MultiTypeWrapper() {}

   MultiTypeWrapper(const H &value) : value_(value) {}

   // If the current constructor does not match the type, pass to its ancestor.
   template <typename C>
   MultiTypeWrapper(const C &value) : MultiTypeWrapper<T...>(value) {}

   operator H () const {
     return value_;
   }
 private:
   H value_;
 };

Avec les constructeurs de conversion implicites, nous pouvons passer quelque chose comme {1,2,5, 'c', 4} à une liste initializer_list (ou un vecteur, qui convertit implicitement la liste initializer_list) de type MultiTypeWrapper. Cela signifie que nous ne pouvons pas écrire une fonction comme ci-dessous pour accepter un tel intializer_list comme argument:

template <typename... T>
std::Tuple<T...> create_Tuple(std::vector<unit_test::MultiTypeWrapper<T...> > init) {
  ....
}

Nous utilisons une autre astuce pour convertir chaque valeur du vecteur en son type d'origine (notez que nous fournissons une conversion implicite dans la définition de MultiTypeWrapper) et l'affecter à l'emplacement correspondant dans un tuple. C'est comme une récursivité sur les arguments du modèle:

template <int ind, typename... T>
class helper {
public:
  static void set_Tuple(std::Tuple<T...> &t, const std::vector<MultiTypeWrapper<T...> >& v) {
    std::get<ind>(t) = static_cast<typename std::Tuple_element<ind,std::Tuple<T...> >::type>(v[ind]);
    helper<(ind-1),T...>::set_Tuple(t,v);
  }
};



template <typename... T>
class helper<0, T...> {
public:
  static void set_Tuple(std::Tuple<T...> &t, const std::vector<MultiTypeWrapper<T...> >& v) {
    std::get<0>(t) = static_cast<typename std::Tuple_element<0,std::Tuple<T...> >::type>(v[0]);
  }
};



template <typename... T>
std::Tuple<T...> create_Tuple(std::vector<unit_test::MultiTypeWrapper<T...> > init) {
  std::Tuple<T...> res;
  helper<sizeof...(T)-1, T...>::set_Tuple(res, init);
  return res;
}

Notez que nous devons créer la classe d'assistance pour set_Tuple puisque c ++ ne prend pas en charge la spécialisation de fonction. Maintenant, si nous voulons tester le code:

auto t = create_Tuple<int,double,std::string>({1,2.5,std::string("ABC")});
printf("%d %.2lf %s\n", std::get<0>(t), std::get<1>(t), std::get<2>(t).c_str());

La sortie serait:

1 2.50 ABC

Ceci est testé sur mon bureau avec clang 3.2

J'espère que ma contribution vous aidera :)

0
BreakDS