web-dev-qa-db-fra.com

détails de std :: make_index_sequence et std :: index_sequence

J'apprécie la montée en puissance des modèles variadic et j'ai commencé à bidouiller avec cette nouvelle fonctionnalité. J'essaie de comprendre les détails de mise en œuvre de std::index_sequence (utilisé pour l'implémentation de Tuple). Je vois un exemple de code là-bas, mais je veux vraiment une explication simplifiée étape par étape de la façon dont un std::index_sequence est codé et le principal de programmation de méta en question pour chaque étape. Pensez vraiment stupéfait :)

12
user3613174

Je vois un exemple de code là-bas, mais je veux vraiment une explication abrégée étape par étape de la façon dont un index_sequence est codé et le principal de méta-programmation en question pour chaque étape.

Ce que vous demandez n'est pas vraiment trivial à expliquer ...

Bien... std::index_sequence lui-même est très simple: est défini comme suit

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

il s'agit essentiellement d'un conteneur de modèle pour un entier non signé.

La partie délicate est l'implémentation de std::make_index_sequence. C'est-à-dire: la partie délicate passe de std::make_index_sequence<N> à std::index_sequence<0, 1, 2, ..., N-1>.

Je vous propose une implémentation possible (pas une grande implémentation mais simple (j'espère) à comprendre) et je vais essayer d'expliquer comment ça marche.

Non exactement la séquence d'index standard, qui passe de std::integer_sequence, mais fixant le std::size_t tapez, vous pouvez obtenir une paire indexSequence/makeIndexSequence raisonnable avec le code suivant.

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

Je suppose qu'une bonne façon de comprendre comment cela fonctionne est de suivre un exemple pratique.

Nous pouvons voir, point à point, comment makeIndexSequence<3> devenir index_sequenxe<0, 1, 2>.

  • Nous avons cela makeIndexSequence<3> est défini comme typename indexSequenceHelper<3>::type [N est 3]

  • indexSequenceHelper<3> ne correspond qu'au cas général donc héritez de indexSequenceHelper<2, 2> [N est 3 et Next... est vide]

  • indexSequenceHelper<2, 2> ne correspond qu'au cas général donc héritez de indexSequenceHelper<1, 1, 2> [N est 2 et Next... est 2]

  • indexSequenceHelper<1, 1, 2> ne correspond qu'au cas général donc héritez de indexSequenceHelper<0, 0, 1, 2> [N est 1 et Next... est 1, 2]

  • indexSequenceHelper<0, 0, 1, 2> correspond aux deux cas (général une spécialisation partielle) donc la spécialisation partielle est appliquée et définit type = indexSequence<0, 1, 2> [Next... est 0, 1, 2]

Conclusion: makeIndexSequence<3> est indexSequence<0, 1, 2>.

J'espère que cela t'aides.

--- MODIFIER ---

Quelques clarifications:

  • std::index_sequence et std::make_index_sequence sont disponibles à partir de C++ 14

  • mon exemple est simple (j'espère) à comprendre mais (comme l'a souligné aschepler) a la grande limite qui est une implémentation linéaire; Je veux dire: si vous avez besoin de index_sequence<0, 1, ... 999>, en utilisant makeIndexSequence<1000> vous implémentez, de manière récursive, 1000 indexSequenceHelper différents; mais il y a une limite de récursivité (compilateur sous forme de compilateur différent) qui peut être inférieure à 1000; il existe d'autres algorithmes qui limitent le nombre de récursions mais sont plus compliqués à expliquer.

18
max66

Par souci d'exhaustivité, je vais ajouter une implémentation plus moderne de std::make_index_sequence, en utilisant if constexpr et auto, qui rendent la programmation de modèles beaucoup plus semblable à la programmation "normale".

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

Je conseille fortement d'utiliser ce style de programmation de modèles, qui est plus facile à raisonner.

7
papagaga