I have written the following basic Tuple template:
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);
}
};
It works just fine, and I can access elements in array-like fashion by using tuple.Get<Index>()
- but I can only do that if I know the index at compile-time. However, I need to access elements in the tuple by index at runtime, and I won't know at compile-time which index needs to be accessed. Example:
int chosenIndex = getUserInput();
void* chosenElement = tuple.Get(chosenIndex);
cout << "The option you chose was: " << ((MyAbstractBaseClass*) chosenElement)->getInfo() << endl;
What's the best way to do this?
EDIT:
Hackish solution below:
Okay, I've got an idea. I already figured out one way of doing this before I even posted this question, but it was hackish and produced warnings. Since another solution isn't forthcoming right away, maybe you guys could help me improve my hackish one. :-)
The tuple can't normally be accessed like an array because the elements are not all necessarily of the same size. (Hence array-style multiplication to arrive at the correct offset in the class structure will not help.) However, I managed to work around this by creating a static table that contains a list of offsets for a tuple. Here's the complete tuple and related templates:
#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 practice it works. However, the compiler gives me a warning for using offsetof on a non-POD data type, and I'm not sure how portable this solution is. Anyone know how I might improve this solution?
See Question&Answers more detail:
os