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 :)
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.
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.