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

时间:2010-12-21 05:01:24

标签: c++ runtime indexing c++11 tuples

我编写了以下基本的Tuple模板:

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>()以类似数组的方式访问元素 - 但我只能在编译时知道索引时才这样做。但是,我需要在运行时通过索引访问元组中的元素,并且我不知道在编译时需要访问哪个索引。例如:

int chosenIndex = getUserInput();
void* chosenElement = tuple.Get(chosenIndex);
cout << "The option you chose was: " << ((MyAbstractBaseClass*) chosenElement)->getInfo() << endl;

最好的方法是什么?

修改

以下黑客解决方案:

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

元组通常不能像数组一样访问,因为元素不一定都是相同的大小。 (因此,在类结构中获得正确的偏移量的数组样式乘法将无济于事。)但是,我设法通过创建一个包含元组偏移列表的静态表来解决这个问题。这是完整的元组和相关模板:

#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,我不确定这个解决方案是多么可移植。有谁知道如何改进这个解决方案?

4 个答案:

答案 0 :(得分:1)

做这样的事情:

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

这使您可以通过逐步检查每个索引来调用具有运行时所选元素的仿函数。它返回函数调用返回的任何内容,但它假定所有调用都生成相同的类型。 (虽然现在,只要所有调用都可以隐式转换为与第一个元素的调用相同的类型,它就会“起作用”。如果你愿意,你可以断言它们都匹配,但这超出了范围这个问题。)

如果编译器没有展开它我会感到惊讶,但我不确定。在任何情况下,它都很简单并且有效(嗯,未经测试,但我认为确实如此),这一点非常重要。

所以无论你想用你的运行时选择元素做什么,都要用它来操作它。您可以进行模拟调用:

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

如果您真的只想将值作为某种类型,那么您可以创建一个简单的仿函数:

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

你可以像这样使用它:

auto x = get<boost::any>(myTuple, i);

要获得void*(yuck),你需要一个最后一个简单的实用程序(太糟糕了,我们没有得到多态lambda):

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

允许:

void* addr = get<get_address>(myTuple, i);

答案 1 :(得分:1)

我很难绕过我找到的解决方案,所以我塑造了自己的解决方案。我元组中的所有成员都来自同一个类,所以我通过在我的元组类中添加一个基类型参数并使用指向成员的指针来调整我以前的解决方案:

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

};

以下现在效果很好,这是我最终拍摄的内容:

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

答案 2 :(得分:0)

首先,为什么要实施std::tuple

其次,不可能在运行时确定的索引处访问tuple,原因是返回类型取决于索引,并且必须在编译时知道函数签名。

您可以通过返回boost::any来解决此问题。

答案 3 :(得分:-1)

你完成与TupleIndexer完全相同的事情,只是在运行时。

将这样的函数添加到Tuple类:

Head &operator[](unsigned i) {
    return i ? ((Tuple<Tail...>&)*this)[i-1] : element;
}

并为Tuple&lt; Head&gt;添加专门化:

Head &operator[](unsigned i) {
    assert(!i);
    return i;
}

(您不能将基本案例放入Tuple&lt;&gt;,因为您没有要返回的类型与每个可能的调用者兼容。)