Ich habe folgenden Grund Tuple Vorlage geschrieben:C++ 0x: Wie kann ich Variadic Tuple-Mitglieder zur Laufzeit auf den Index zugreifen?
template <typename... T>
class Tuple;
template <uintptr_t N, typename... T>
struct TupleIndexer;
template <typename Head, typename... Tail>
class Tuple<Head, Tail...> : public Tuple<Tail...> {
private:
Head element;
public:
template <uintptr_t N>
typename TupleIndexer<N, Head, Tail...>::Type& Get() {
return TupleIndexer<N, Head, Tail...>::Get(*this);
}
uintptr_t GetCount() const {
return sizeof...(Tail) + 1;
}
private:
friend struct TupleIndexer<0, Head, Tail...>;
};
template <>
class Tuple<> {
public:
uintptr_t GetCount() const {
return 0;
}
};
template <typename Head, typename... Tail>
struct TupleIndexer<0, Head, Tail...> {
typedef Head& Type;
static Type Get(Tuple<Head, Tail...>& tuple) {
return tuple.element;
}
};
template <uintptr_t N, typename Head, typename... Tail>
struct TupleIndexer<N, Head, Tail...> {
typedef typename TupleIndexer<N - 1, Tail...>::Type Type;
static Type Get(Tuple<Head, Tail...>& tuple) {
return TupleIndexer<N - 1, Tail...>::Get(*(Tuple<Tail...>*) &tuple);
}
};
Es funktioniert ganz gut, und ich kann tuple.Get<Index>()
Elemente in Array-ähnlichen Art und Weise zugreifen, indem Sie mit - aber ich kann nur das tun, wenn ich weiß, den Index bei der Kompilierung -Zeit. Allerdings muss ich zur Laufzeit auf Elemente im Tupel per Index zugreifen, und ich weiß zur Kompilierzeit nicht, auf welchen Index zugegriffen werden muss. Beispiel:
int chosenIndex = getUserInput();
void* chosenElement = tuple.Get(chosenIndex);
cout << "The option you chose was: " << ((MyAbstractBaseClass*) chosenElement)->getInfo() << endl;
Was ist der beste Weg, dies zu tun?
EDIT:
hackish Lösung unter:
Okay, ich habe eine Idee. Ich habe bereits einen Weg gefunden, dies zu tun, bevor ich diese Frage überhaupt gestellt habe, aber es war hackisch und gab Warnungen ab. Da eine andere Lösung nicht sofort verfügbar ist, könntest du mir vielleicht helfen, meine hackliche zu verbessern. :-)
Das Tupel kann normalerweise nicht wie ein Array angesprochen werden, da die Elemente nicht notwendigerweise alle die gleiche Größe haben. (Daher hilft die array-artige Multiplikation, um den korrekten Offset in der Klassenstruktur zu erreichen.) Es ist mir jedoch gelungen, dies zu umgehen, indem ich eine statische Tabelle mit einer Liste von Offsets für ein Tupel erstelle. Hier ist das vollständige Tupel und verwandte Vorlagen:
#include <cstddef>
template <typename... T>
class Tuple;
template <uintptr_t N, typename... T>
struct TupleIndexer;
template <typename... T>
struct TupleOffsets;
template <typename Head, typename... Tail>
struct TupleOffsets<Head, Tail...> {
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 <typename Head, typename... Tail>
void TupleOffsets<Head, Tail...>::Init(uintptr_t* offsets) {
typedef Tuple<Head, Tail...> Type;
*offsets = offsetof(Type, element);
TupleOffsets<Tail...>::Init(++offsets);
}
template <>
struct TupleOffsets<> {
TupleOffsets() {}
static void Init(uintptr_t* offsets) {}
};
template <typename Head, typename... Tail>
class Tuple<Head, Tail...> : public Tuple<Tail...> {
private:
Head element;
public:
void* Get(uintptr_t i) {
return (uint8_t*) this + offsets[i];
}
template <uintptr_t N>
typename TupleIndexer<N, Head, Tail...>::Type& Get() {
return TupleIndexer<N, Head, Tail...>::Get(*this);
}
uintptr_t GetCount() const {
return sizeof...(Tail) + 1;
}
private:
static const TupleOffsets<Head, Tail...> offsets;
friend struct TupleOffsets<Head, Tail...>;
friend struct TupleIndexer<0, Head, Tail...>;
};
template <typename Head, typename... Tail>
const TupleOffsets<Head, Tail...> Tuple<Head, Tail...>::offsets;
template <>
class Tuple<> {
public:
uintptr_t GetCount() const {
return 0;
}
};
template <typename Head, typename... Tail>
struct TupleIndexer<0, Head, Tail...> {
typedef Head& Type;
static Type Get(Tuple<Head, Tail...>& tuple) {
return tuple.element;
}
};
template <uintptr_t N, typename Head, typename... Tail>
struct TupleIndexer<N, Head, Tail...> {
typedef typename TupleIndexer<N - 1, Tail...>::Type Type;
static Type Get(Tuple<Head, Tail...>& tuple) {
return TupleIndexer<N - 1, Tail...>::Get(*(Tuple<Tail...>*) &tuple);
}
};
In der Praxis funktioniert es. Der Compiler gibt mir jedoch eine Warnung für die Verwendung von Offsetof auf einem Nicht-POD-Datentyp, und ich bin mir nicht sicher, wie tragbar diese Lösung ist. Wer weiß, wie ich diese Lösung verbessern könnte?
Hmm, diese Lösung würde allerdings keine linearen Zugriffszeiten bieten, oder? (Ein theoretisches Tupel mit einem Dutzend Elementen müsste z. B. ein Dutzend Mal rekursiv sein, um das letzte Element zurückzugeben.) Zur Kompilierungszeit kann alles inline sein, aber nicht zur Laufzeit. – nonoitall
Dies setzt voraus, dass alle Typen im 'Tuple'' Head' sind, wodurch 'Tuple' überflüssig wird. – Motti
@Motti: Meine Gedanken genau, std :: Array wäre eine bessere Passform –