C++0x: How can I access variadic tuple members by index at runtime?
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, 开发者_StackOverflowTail...>;
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?
Do something like this:
namespace detail
{
template <std::size_t I, typename R, typename Tuple, typename Func>
R select(Tuple&& pTuple, Func pFunc)
{
return pFunc(get<I>(std::forward<Tuple>(pTuple)));
}
template <std::size_t I, typename R, typename Tuple, typename Func>
R select_element(Tuple&& pTuple, std::size_t pIndex, Func pFunc)
{
if (pIndex == I)
return select<I, R>(std::forward<Tuple>(pTuple), pFunc);
else
return select<I + 1, R>(std::forward<Tuple>(pTuple), pIndex, pFunc);
}
}
template <typename Tuple, typename Func>
R select(Tuple&& pTuple, std::size_t pIndex, Func pFunc)
{
typedef typename std::remove_reference<Tuple>::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<Func, dummy_type>::type result_type;
if (pIndex >= std::tuple_size<tuple_type>::value)
throw std::out_of_range("select out of range");
return detail::select<0, result_type>(
std::forward<Tuple>(pTuple), pIndex, pFunc);
}
This lets you call a functor with a run-time selected element, by checking each index incrementally. It returns whatever the function call returns, but it assumes that all invocations result in the same type. (Though right now, it'll "work" as long as all invocations happen to be implicitly convertible to the same type as an invocation of the first element. You can assert they all match if you want to, but that's outside the scope of this question.)
I'd be surprised if the compiler didn't unroll it, but I don't know for certain. In any case, it's simple and works (well, untested, but I assume it does) and that's far more important.
So whatever you wanted to do with your run-time selected element, operate on it with this. You can make the call templated:
struct print_element
{
// T is determined at compile time for each possible element type,
// but which overload gets selected is determined at run-time
template <typename T>
void operator()(const T& pX) const
{
std::cout << pX << std::endl;
}
};
If you really just want the value as some type, then you can make a simple functor:
namespace detail
{
template <typename R>
struct get_element
{
template <typename T>
R operator()(T&& pValue) const
{
return std::forward<T>(pValue);
}
};
}
template <typename R, typename Tuple>
R get(Tuple&& pTuple, std::size_t pIndex)
{
return select(std::forward<Tuple>(pTuple), pIndex, get_element<R>());
}
You can use it like this:
auto x = get<boost::any>(myTuple, i);
To get void*
's (yuck), you need one last simple utility (too bad we don't get polymorphic lambda's):
class get_address
{
public:
template <typename T>
get_address(T& pValue) :
mResult(&pValue)
{}
void* get() const
{
return mResult;
}
operator void*() const
{
return get();
}
private:
void* mResult;
};
Allowing:
void* addr = get<get_address>(myTuple, i);
I was having a hard time wrapping my head around the solutions I was finding, so I fashioned one of my own. All of the members in my tuple derive from the same class, so I adapted my previous solution by adding a base type parameter to my tuple class and using pointers-to-members:
template <typename Base, typename... T>
class Tuple;
template <typename Base, uintptr_t N, typename... T>
struct TupleIndexer;
template <typename Base, typename... T>
struct TupleOffsets;
template <typename Base, typename Head, typename... Tail>
struct TupleOffsets<Base, Head, Tail...> {
TupleOffsets() { Init<Base Tuple<Base, Head, Tail...>::*>(offsets); }
Base Tuple<Base, Head, Tail...>::* const& operator[] (uintptr_t i) const { return offsets[i]; }
template <typename PtrType>
static void Init(PtrType* offsets);
private:
Base Tuple<Base, Head, Tail...>::* offsets[sizeof...(Tail) + 1];
};
template <typename Base, typename Head, typename... Tail>
template <typename PtrType>
void TupleOffsets<Base, Head, Tail...>::Init(PtrType* offsets) {
*offsets = PtrType(&Tuple<Base, Head, Tail...>::element);
TupleOffsets<Base, Tail...>::Init(++offsets);
}
template <typename Base>
struct TupleOffsets<Base> {
TupleOffsets() {}
template <typename PtrType>
static void Init(PtrType* offsets) {}
};
template <typename Base, typename Head, typename... Tail>
class Tuple<Base, Head, Tail...> : public Tuple<Base, Tail...> {
private:
Head element;
public:
Base* Get(uintptr_t i) {
return &(this->*offsets[i]);
}
template <uintptr_t N>
typename TupleIndexer<Base, N, Head, Tail...>::Type& Get() {
return TupleIndexer<Base, N, Head, Tail...>::Get(*this);
}
uintptr_t GetCount() const {
return sizeof...(Tail) + 1;
}
private:
static const TupleOffsets<Base, Head, Tail...> offsets;
friend struct TupleOffsets<Base, Head, Tail...>;
friend struct TupleIndexer<Base, 0, Head, Tail...>;
};
template <typename Base, typename Head, typename... Tail>
const TupleOffsets<Base, Head, Tail...> Tuple<Base, Head, Tail...>::offsets;
template <typename Base>
class Tuple<Base> {
public:
uintptr_t GetCount() const {
return 0;
}
};
template <typename Base, typename Head, typename... Tail>
struct TupleIndexer<Base, 0, Head, Tail...> {
typedef Head& Type;
static Type Get(Tuple<Base, Head, Tail...>& tuple) {
return tuple.element;
}
};
template <typename Base, uintptr_t N, typename Head, typename... Tail>
struct TupleIndexer<Base, N, Head, Tail...> {
typedef typename TupleIndexer<Base, N - 1, Tail...>::Type Type;
static Type Get(Tuple<Base, Head, Tail...>& tuple) {
return TupleIndexer<Base, N - 1, Tail...>::Get(*(Tuple<Base, Tail...>*) &tuple);
}
};
The following now works nicely, which is what I was ultimately shooting for:
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<Base, Derived1, Derived2> var;
var.Get(0)->print();
var.Get(1)->print();
First of all why are you implementing std::tuple
?
Secondly it's not possible to access a tuple
at a runtime determined index, the reason being that the return type depends on the index and the function signature must be known at compile time.
You may be able to work around this problem by returning a boost::any
.
You do the exact same thing as you do with TupleIndexer, just at runtime.
Add a function like this to the Tuple class:
Head &operator[](unsigned i) {
return i ? ((Tuple<Tail...>&)*this)[i-1] : element;
}
and add a specialization for Tuple<Head>:
Head &operator[](unsigned i) {
assert(!i);
return i;
}
(You can't put the base case into Tuple<>, since you don't have a type to return that would be compatible with every possible caller.)
精彩评论