C ++ 0x: Как я могу получить доступ к элементам кортежа с переменным индексом во время выполнения? - PullRequest
3 голосов
/ 21 декабря 2010

Я написал следующий базовый шаблон 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<<em>Index</em>>() - но я могу сделать это только если язнать индекс во время компиляции.Однако мне нужно обращаться к элементам в кортеже по индексу во время выполнения, и я не буду знать во время компиляции, какой индекс должен быть доступен.Пример:

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

Каков наилучший способ сделать это?

РЕДАКТИРОВАТЬ:

Хакерское решение ниже:

Хорошо, у меня есть идея.Я уже придумал один способ сделать это еще до того, как опубликовал этот вопрос, но он был хакерским и вызывал предупреждения.Так как другого решения не ожидается сразу, возможно, вы, ребята, могли бы помочь мне улучшить мой хакерский.: -)

К кортежу обычно нельзя получить доступ как к массиву, потому что элементы не обязательно имеют одинаковый размер.(Следовательно, умножение в стиле массива для получения правильного смещения в структуре класса не поможет.) Однако мне удалось обойти это, создав статическую таблицу, которая содержит список смещений для кортежа.Вот полный набор кортежей и связанных с ними шаблонов:

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

};

На практике это работает.Тем не менее, компилятор выдает предупреждение за использование offsetof для типов данных, отличных от POD, и я не уверен, насколько это решение переносимо.Кто-нибудь знает, как я могу улучшить это решение?

Ответы [ 4 ]

1 голос
/ 26 декабря 2010

Мне было трудно обдумывать решения, которые я нашел, поэтому я создал свое собственное. Все члены в моем кортеже происходят из одного и того же класса, поэтому я адаптировал свое предыдущее решение, добавив параметр базового типа в мой класс кортежа и используя указатели на члены:

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();
1 голос
/ 21 декабря 2010

Сделайте что-то вроде этого:

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), вам нужна последняя простая утилита (очень жаль, что мы не получаем полиморфную лямбду):

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);
0 голосов
/ 21 декабря 2010

Прежде всего, почему вы внедряете std::tuple?

Во-вторых, невозможно получить доступ к tuple по индексу, определенному во время выполнения, так как тип возвращаемого значения зависит от индекса, а сигнатура функции должна быть известна во время компиляции.

Вы можете обойти эту проблему, вернув boost::any.

0 голосов
/ 21 декабря 2010

Вы делаете то же самое, что и с TupleIndexer, только во время выполнения.

Добавьте такую ​​функцию в класс Tuple:

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

и добавьте специализацию для Tuple :

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

(Вы не можете поместить базовый регистр в Tuple <>, поскольку у вас нет типа для возврата, который был бы совместим со всеми возможными вызывающими.)

...