Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
245 views
in Technique[技术] by (71.8m points)

c++ - 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, 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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...