2010-12-21 10 views
3

Ich habe folgenden Grund Tuple Vorlage geschrieben:C++ 0x: Wie kann ich Variadic Tuple-Mitglieder zur Laufzeit auf den Index zugreifen?

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

}; 

Es funktioniert ganz gut, und ich kann tuple.Get<Index>() Elemente in Array-ähnlichen Art und Weise zugreifen, indem Sie mit - aber ich kann nur das tun, wenn ich weiß, den Index bei der Kompilierung -Zeit. Allerdings muss ich zur Laufzeit auf Elemente im Tupel per Index zugreifen, und ich weiß zur Kompilierzeit nicht, auf welchen Index zugegriffen werden muss. Beispiel:

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

Was ist der beste Weg, dies zu tun?

EDIT:

hackish Lösung unter:

Okay, ich habe eine Idee. Ich habe bereits einen Weg gefunden, dies zu tun, bevor ich diese Frage überhaupt gestellt habe, aber es war hackisch und gab Warnungen ab. Da eine andere Lösung nicht sofort verfügbar ist, könntest du mir vielleicht helfen, meine hackliche zu verbessern. :-)

Das Tupel kann normalerweise nicht wie ein Array angesprochen werden, da die Elemente nicht notwendigerweise alle die gleiche Größe haben. (Daher hilft die array-artige Multiplikation, um den korrekten Offset in der Klassenstruktur zu erreichen.) Es ist mir jedoch gelungen, dies zu umgehen, indem ich eine statische Tabelle mit einer Liste von Offsets für ein Tupel erstelle. Hier ist das vollständige Tupel und verwandte Vorlagen:

#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 der Praxis funktioniert es. Der Compiler gibt mir jedoch eine Warnung für die Verwendung von Offsetof auf einem Nicht-POD-Datentyp, und ich bin mir nicht sicher, wie tragbar diese Lösung ist. Wer weiß, wie ich diese Lösung verbessern könnte?

Antwort

1

Ich war rund um die Lösungen eine harte Zeit, schlinge meinen Kopf Ich war zu finden, so fashioned ich einen meiner eigenen. Alle Mitglieder in meinem Tupel aus der gleichen Klasse abgeleitet werden, so angepasst ich meine bisherige Lösung eines Basistyp-Parameter auf meine Tupel-Klasse und mit Zeigern-to-Mitglieder durch Zugabe:

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

}; 

Die folgenden jetzt funktionieren gut, wofür ich letztendlich fotografiert habe:

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

Sie tun genau das gleiche wie Sie mit TupleIndexer tun, nur zur Laufzeit.

eine Funktion wie diese in die Tuple-Klasse hinzufügen:

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

und eine Spezialisierung für Tuple < Kopf hinzufügen >:

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

(Sie können den Basisfall in Tuple setzen < >, da Sie keinen zurückzugebenden Typ haben, der mit jedem möglichen Anrufer kompatibel wäre.)

+0

Hmm, diese Lösung würde allerdings keine linearen Zugriffszeiten bieten, oder? (Ein theoretisches Tupel mit einem Dutzend Elementen müsste z. B. ein Dutzend Mal rekursiv sein, um das letzte Element zurückzugeben.) Zur Kompilierungszeit kann alles inline sein, aber nicht zur Laufzeit. – nonoitall

+1

Dies setzt voraus, dass alle Typen im 'Tuple'' Head' sind, wodurch 'Tuple' überflüssig wird. – Motti

+0

@Motti: Meine Gedanken genau, std :: Array wäre eine bessere Passform –

0

Warum implementieren Sie zuerst std::tuple?

Zweitens ist es nicht möglich, einen tuple auf einen Runtime ermittelten Index zuzugreifen, da der Rückgabetyp vom Index abhängt und die Funktionssignatur zur Kompilierungszeit bekannt sein muss. Sie können dieses Problem möglicherweise umgehen, indem Sie boost::any zurückgeben.

+0

Sie haben Recht; Es gibt keine Möglichkeit, den Rückgabetyp basierend auf Laufzeitdaten zu kennen. Aber eine Methode, die ein void * an das Mitglied zurückgibt, wäre für meine Zwecke angemessen. (Ich habe mein Beispiel oben bearbeitet, um dies zu verdeutlichen.) – nonoitall

1

etwas tun:

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

Auf diese Weise können Sie einen Funktor mit einem Laufzeit ausgewählte Element aufrufen, indem jeder Index inkrementell zu überprüfen.Es gibt zurück, was auch immer der Funktionsaufruf zurückgibt, aber es nimmt an, dass alle Aufrufe denselben Typ ergeben. (Allerdings wird es "funktionieren", solange alle Aufrufe implizit in den gleichen Typ konvertierbar sind wie ein Aufruf des ersten Elements. Sie können behaupten, dass alle übereinstimmen, wenn Sie möchten, aber das liegt außerhalb des Bereichs von Diese Frage.)

Ich wäre überrascht, wenn der Compiler es nicht ausrollen würde, aber ich weiß es nicht sicher. In jedem Fall ist es einfach und funktioniert (gut, ungeprüft, aber ich nehme an, es tut) und das ist viel wichtiger.

Also was auch immer Sie mit Ihrem Laufzeitelement tun wollten, arbeiten Sie damit. Sie können den Anruf als Templat machen:

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

Wenn Sie wirklich nur den Wert als eine Art wollen, dann können Sie eine einfache Funktors machen:

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

Sie können es wie folgt verwenden:

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

Um void* ‚s (igitt) bekommen Sie ein letztes einfaches Programm benötigen (schade, dass wir nicht polymorphe des Lambda erhalten):

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

erlauben:

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