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.)