C++0x:如何在运行时通过索引访问可变元组成员?

2022-01-20 00:00:00 indexing tuples runtime c++ c++11

我已经编写了以下基本元组模板:

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

};

它工作得很好,我可以使用 tuple.Get<Index>() 以类似数组的方式访问元素 - 但我只能这样做,如果我知道编译时的索引.但是,我需要在运行时按索引访问元组中的元素,并且在编译时我不知道需要访问哪个索引.示例:

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;

最好的方法是什么?

以下黑客解决方案:

好的,我有个主意.在我发布这个问题之前,我已经想出了一种方法,但它是hackish并且产生了警告.由于另一种解决方案不会马上出现,也许你们可以帮助我改进我的hackish 解决方案.:-)

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

};

在实践中它是有效的.但是,编译器警告我在非 POD 数据类型上使用 offsetof,我不确定这个解决方案的可移植性如何.有谁知道我可以如何改进这个解决方案?

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?

推荐答案

我很难理解我正在寻找的解决方案,所以我自己设计了一个.我的元组中的所有成员都派生自同一个类,因此我通过向我的元组类添加一个基类型参数并使用指向成员的指针来调整我之前的解决方案:

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

相关文章