wake-up-neo.net

Details zu std :: make_index_sequence und std :: index_sequence

Ich arbeite gerne an verschiedenen Vorlagen und habe angefangen, mit dieser neuen Funktion herumzuspielen. Ich versuche, mich mit den Implementierungsdetails von std::index_sequence (Verwendet für die Tuple-Implementierung) vertraut zu machen. Ich sehe dort einen Beispielcode, aber ich möchte wirklich eine schrittweise Erklärung, wie ein std::index_sequence Codiert ist, und das fragliche Metaprogrammierungsprinzip für jede Stufe. Denken Sie wirklich niedergeschlagen :)

12
user3613174

Ich sehe dort Beispielcode, aber ich möchte wirklich eine schrittweise Erklärung darüber, wie eine index_sequence codiert ist und welches Meta-Programmierprinzip für jede Stufe in Frage kommt.

Was Sie fragen, ist nicht gerade trivial zu erklären ...

Nun ... std::index_sequence Selbst ist sehr einfach: wie folgt definiert

template<std::size_t... Ints>
using index_sequence = std::integer_sequence<std::size_t, Ints...>;

dies ist im Wesentlichen ein Vorlagencontainer für eine ganze Zahl ohne Vorzeichen.

Der knifflige Teil ist die Implementierung von std::make_index_sequence. Das heißt: Der schwierige Teil ist die Übergabe von std::make_index_sequence<N> An std::index_sequence<0, 1, 2, ..., N-1>.

Ich schlage Ihnen eine mögliche Implementierung vor (keine großartige Implementierung, aber einfach (ich hoffe) zu verstehen) und ich werde versuchen zu erklären, wie es funktioniert.

Nicht genau die Standard-Indexsequenz, die von std::integer_sequence Übergeben wird. Wenn Sie jedoch den Typ std::size_t Korrigieren, erhalten Sie ein angemessenes indexSequence/makeIndexSequence-Paar mit dem folgenden Code .

// index sequence only
template <std::size_t ...>
struct indexSequence
 { };

template <std::size_t N, std::size_t ... Next>
struct indexSequenceHelper : public indexSequenceHelper<N-1U, N-1U, Next...>
 { };

template <std::size_t ... Next>
struct indexSequenceHelper<0U, Next ... >
 { using type = indexSequence<Next ... >; };

template <std::size_t N>
using makeIndexSequence = typename indexSequenceHelper<N>::type;

Ich nehme an, dass ein praktisches Beispiel ein guter Weg ist, um zu verstehen, wie es funktioniert.

Wir können von Punkt zu Punkt sehen, wie makeIndexSequence<3> Zu index_sequenxe<0, 1, 2> Wird.

  • Wir haben, dass makeIndexSequence<3> Definiert ist als typename indexSequenceHelper<3>::type [N ist 3]

  • indexSequenceHelper<3> Stimmt nur mit dem allgemeinen Fall überein, also ist indexSequenceHelper<2, 2> [N3 Und Next... Leer]

  • indexSequenceHelper<2, 2> Stimmt nur mit dem allgemeinen Fall überein, der von indexSequenceHelper<1, 1, 2> [N geerbt wird, ist 2 Und Next... Ist 2.

  • indexSequenceHelper<1, 1, 2> Stimmt nur mit dem allgemeinen Fall überein, der von indexSequenceHelper<0, 0, 1, 2> [N geerbt wird, ist 1 Und Next... Ist 1, 2.

  • indexSequenceHelper<0, 0, 1, 2> Stimmen mit beiden Fällen überein (allgemein eine Teilspezialisierung), sodass die Teilspezialisierung angewendet wird, und definieren Sie, dass type = indexSequence<0, 1, 2> [Next...0, 1, 2 Ist.

Schlussfolgerung: makeIndexSequence<3> Ist indexSequence<0, 1, 2>.

Hoffe das hilft.

--- EDIT ---

Einige Klarstellungen:

  • std::index_sequence Und std::make_index_sequence Sind ab C++ 14 verfügbar

  • mein Beispiel ist einfach (ich hoffe) zu verstehen, hat aber (wie von Aschepler angedeutet) die große Grenze, die eine lineare Implementierung ist. Ich meine: Wenn Sie index_sequence<0, 1, ... 999> Benötigen, implementieren Sie mit makeIndexSequence<1000> Rekursiv 1000 verschiedene indexSequenceHelper; Es gibt jedoch ein Rekursionslimit (Compiler vom Compiler verschieden), das unter 1000 liegen kann. Es gibt andere Algorithmen, die die Anzahl der Rekursionen begrenzen, deren Erklärung jedoch komplizierter ist.

18
max66

Der Vollständigkeit halber füge ich eine modernere Implementierung von std::make_index_sequence mit if constexpr und auto, die die Template-Programmierung viel mehr wie "normale" Programmierung machen.

template <std::size_t... Ns>
struct index_sequence {};

template <std::size_t N, std::size_t... Is>
auto make_index_sequence_impl() {
    // only one branch is considered. The other may be ill-formed
    if constexpr (N == 0) return index_sequence<Is...>(); // end case
    else return make_index_sequence_impl<N-1, N-1, Is...>(); // recursion
}

template <std::size_t N>
using make_index_sequence = std::decay_t<decltype(make_index_sequence_impl<N>())>;

Ich rate dringend dazu, diesen Stil der Vorlagenprogrammierung zu verwenden, über den man leichter nachdenken kann.

7
papagaga