C ++ 0x: Comment puis-je accéder aux membres de tuple variadic par index lors de l’exécution?

J’ai écrit le modèle de base de Tuple suivant:

template  class Tuple; template  struct TupleIndexer; template  class Tuple : public Tuple { private: Head element; public: template  typename TupleIndexer::Type& Get() { return TupleIndexer::Get(*this); } uintptr_t GetCount() const { return sizeof...(Tail) + 1; } private: friend struct TupleIndexer; }; template  class Tuple { public: uintptr_t GetCount() const { return 0; } }; template  struct TupleIndexer { typedef Head& Type; static Type Get(Tuple& tuple) { return tuple.element; } }; template  struct TupleIndexer { typedef typename TupleIndexer::Type Type; static Type Get(Tuple& tuple) { return TupleIndexer::Get(*(Tuple*) &tuple); } }; 

Cela fonctionne très bien et je peux accéder aux éléments de la même manière que des tableaux en utilisant tuple.Get< Index >() – mais je ne peux le faire que si je connais l’index au moment de la compilation. Cependant, je dois accéder aux éléments du tuple par index au moment de l’exécution et je ne saurai pas au moment de la compilation quel index il faut accéder. Exemple:

 int chosenIndex = getUserInput(); void* chosenElement = tuple.Get(chosenIndex); cout << "The option you chose was: " <getInfo() << endl; 

Quelle est la meilleure façon de faire cela?

MODIFIER:

Solution Hackish ci-dessous:

Ok, j’ai une idée. J’avais déjà trouvé un moyen de le faire avant même de poster cette question, mais c’était hackish et produisait des avertissements. Puisqu’une autre solution ne se présente pas tout de suite, peut-être que vous pourriez m’aider à améliorer ma solution de piratage. 🙂

Le tuple ne peut normalement pas être accédé comme un tableau car les éléments n’ont pas tous nécessairement la même taille. (Par conséquent, la multiplication de type tableau pour arriver au décalage correct dans la structure de classe ne vous aidera pas.) Cependant, j’ai réussi à contourner ce problème en créant une table statique contenant une liste de décalages pour un tuple. Voici le tuple complet et les modèles associés:

 #include  template  class Tuple; template  struct TupleIndexer; template  struct TupleOffsets; template  struct TupleOffsets { TupleOffsets() { Init(offsets); } static void Init(uintptr_t* offsets); uintptr_t const& operator[] (uintptr_t i) const { return offsets[i]; } private: uintptr_t offsets[sizeof...(Tail) + 1]; }; template  void TupleOffsets::Init(uintptr_t* offsets) { typedef Tuple Type; *offsets = offsetof(Type, element); TupleOffsets::Init(++offsets); } template  struct TupleOffsets { TupleOffsets() {} static void Init(uintptr_t* offsets) {} }; template  class Tuple : public Tuple { private: Head element; public: void* Get(uintptr_t i) { return (uint8_t*) this + offsets[i]; } template  typename TupleIndexer::Type& Get() { return TupleIndexer::Get(*this); } uintptr_t GetCount() const { return sizeof...(Tail) + 1; } private: static const TupleOffsets offsets; friend struct TupleOffsets; friend struct TupleIndexer; }; template  const TupleOffsets Tuple::offsets; template  class Tuple { public: uintptr_t GetCount() const { return 0; } }; template  struct TupleIndexer { typedef Head& Type; static Type Get(Tuple& tuple) { return tuple.element; } }; template  struct TupleIndexer { typedef typename TupleIndexer::Type Type; static Type Get(Tuple& tuple) { return TupleIndexer::Get(*(Tuple*) &tuple); } }; 

En pratique ça marche. Cependant, le compilateur me prévient d’utiliser offsetof sur un type de données autre que POD, et je ne sais pas dans quelle mesure cette solution est portable. Quelqu’un sait comment je pourrais améliorer cette solution?

Faites quelque chose comme ça:

 namespace detail { template  R select(Tuple&& pTuple, Func pFunc) { return pFunc(get(std::forward(pTuple))); } template  R select_element(Tuple&& pTuple, std::size_t pIndex, Func pFunc) { if (pIndex == I) return select(std::forward(pTuple), pFunc); else return select(std::forward(pTuple), pIndex, pFunc); } } template  R select(Tuple&& pTuple, std::size_t pIndex, Func pFunc) { typedef typename std::remove_reference::type tuple_type; // assumes all possible calls to Func return the same type typedef typename std::tuple_element<0, tuple_type>::type dummy_type; typedef typename std::result_of::type result_type; if (pIndex >= std::tuple_size::value) throw std::out_of_range("select out of range"); return detail::select<0, result_type>( std::forward(pTuple), pIndex, pFunc); } 

Cela vous permet d’appeler un foncteur avec un élément d’exécution sélectionné, en vérifiant chaque index de manière incrémentielle. Il retourne quelle que soit l’appel de fonction, mais il suppose que toutes les invocations donnent le même type. (Bien que maintenant, cela fonctionnera aussi longtemps que toutes les invocations seront implicitement convertibles au même type qu’une invocation du premier élément. Vous pouvez affirmer qu’elles correspondent toutes si vous voulez, mais cela sort du cadre de cette question.)

Je serais surpris que le compilateur ne l’ait pas déroulé, mais je n’en suis pas certain. En tout cas, c’est simple et fonctionne (enfin, non testé, mais je suppose que c’est le cas) et c’est beaucoup plus important.

Donc, quoi que vous vouliez faire avec votre élément sélectionné au moment de l’exécution, utilisez-le avec ceci. Vous pouvez effectuer l’appel basé sur un modèle:

 struct print_element { // T is determined at comstack time for each possible element type, // but which overload gets selected is determined at run-time template  void operator()(const T& pX) const { std::cout << pX << std::endl; } }; 

Si vous voulez vraiment que la valeur soit comme un type, alors vous pouvez faire un simple foncteur:

 namespace detail { template  struct get_element { template  R operator()(T&& pValue) const { return std::forward(pValue); } }; } template  R get(Tuple&& pTuple, std::size_t pIndex) { return select(std::forward(pTuple), pIndex, get_element()); } 

Vous pouvez l'utiliser comme ceci:

 auto x = get(myTuple, i); 

Pour obtenir les void* (beurk), vous avez besoin d'un dernier utilitaire simple (dommage que nous n'ayons pas de lambda polymorphe):

 class get_address { public: template  get_address(T& pValue) : mResult(&pValue) {} void* get() const { return mResult; } operator void*() const { return get(); } private: void* mResult; }; 

En permettant:

 void* addr = get(myTuple, i); 

J’avais du mal à comprendre les solutions que je trouvais et j’ai donc créé l’une des miennes. Tous les membres de mon tuple sont issus de la même classe. J’ai donc adapté ma solution précédente en ajoutant un paramètre de type de base à ma classe de tuple et en utilisant des pointeurs vers les membres:

 template  class Tuple; template  struct TupleIndexer; template  struct TupleOffsets; template  struct TupleOffsets { TupleOffsets() { Init::*>(offsets); } Base Tuple::* const& operator[] (uintptr_t i) const { return offsets[i]; } template  static void Init(PtrType* offsets); private: Base Tuple::* offsets[sizeof...(Tail) + 1]; }; template  template  void TupleOffsets::Init(PtrType* offsets) { *offsets = PtrType(&Tuple::element); TupleOffsets::Init(++offsets); } template  struct TupleOffsets { TupleOffsets() {} template  static void Init(PtrType* offsets) {} }; template  class Tuple : public Tuple { private: Head element; public: Base* Get(uintptr_t i) { return &(this->*offsets[i]); } template  typename TupleIndexer::Type& Get() { return TupleIndexer::Get(*this); } uintptr_t GetCount() const { return sizeof...(Tail) + 1; } private: static const TupleOffsets offsets; friend struct TupleOffsets; friend struct TupleIndexer; }; template  const TupleOffsets Tuple::offsets; template  class Tuple { public: uintptr_t GetCount() const { return 0; } }; template  struct TupleIndexer { typedef Head& Type; static Type Get(Tuple& tuple) { return tuple.element; } }; template  struct TupleIndexer { typedef typename TupleIndexer::Type Type; static Type Get(Tuple& tuple) { return TupleIndexer::Get(*(Tuple*) &tuple); } }; 

Ce qui suit fonctionne maintenant très bien, et c’est ce pour quoi je voulais en venir:

 struct Base { virtual void print() = 0; }; struct Derived1 : public Base { virtual void print() { cout << "I'm the first derived class!" << endl; } }; struct Derived2 : public Base { virtual void print() { cout << "Woohoo! I'm the second derived class!" << endl; } }; ... Tuple var; var.Get(0)->print(); var.Get(1)->print(); 

Tout d’abord, pourquoi implémentez-vous std::tuple ?

Deuxièmement, il n’est pas possible d’accéder à un tuple à un index d’exécution déterminé, la raison étant que le type de retour dépend de l’index et que la signature de la fonction doit être connue au moment de la compilation.

Vous pourrez peut-être contourner ce problème en renvoyant un boost::any .

Vous faites exactement la même chose qu’avec TupleIndexer, juste au moment de l’exécution.

Ajoutez une fonction comme celle-ci à la classe Tuple:

 Head &operator[](unsigned i) { return i ? ((Tuple&)*this)[i-1] : element; } 

et ajoutez une spécialisation pour Tuple :

 Head &operator[](unsigned i) { assert(!i); return i; } 

(Vous ne pouvez pas mettre le scénario de base dans le tuple <>, car vous n’avez pas à renvoyer le type qui serait compatible avec tous les appelants possibles.)