Ich genieße es, mich mit variadischen Vorlagen zu beschäftigen, und habe angefangen, mit dieser neuen Funktion herumzuspielen. Ich versuche, meinen Kopf um die Implementierungsdetails von zu bekommen std::index_sequence
‘s (wird für die Tupelimplementierung verwendet). Ich sehe dort Beispielcode, aber ich möchte wirklich eine verdummte Schritt-für-Schritt-Erklärung, wie ein std::index_sequence
ist kodiert und das betreffende Meta-Programmierprinzip für jede Stufe. Überlegen Ja wirklich verdummt 🙂
Details zu std::make_index_sequence und std::index_sequence
Benutzer3613174
max66
Ich sehe dort Beispielcode, aber ich möchte wirklich eine verdummte Schritt-für-Schritt-Erklärung, wie eine index_sequence codiert wird, und das fragliche Meta-Programmierprinzip für jede Phase.
Was Sie fragen, ist nicht gerade trivial zu erklären …
Brunnen… std::index_sequence
selbst ist sehr einfach: ist wie folgt definiert
template<std::size_t... Ints>
using index_sequence = std::integer_sequence<std::size_t, Ints...>;
das ist im Wesentlichen ein Vorlagencontainer für vorzeichenlose Ganzzahlen.
Der knifflige Teil ist die Implementierung von std::make_index_sequence
. Das heißt: Der knifflige Teil ist Pass aus std::make_index_sequence<N>
zu std::index_sequence<0, 1, 2, ..., N-1>
.
Ich schlage Ihnen eine mögliche Implementierung vor (keine großartige Implementierung, aber (hoffentlich) einfach zu verstehen) und ich werde versuchen zu erklären, wie sie funktioniert.
Nicht genau die Standard-Index-Sequenz, die abgeht std::integer_sequence
, aber Festsetzung der std::size_t
Typ können Sie eine vernünftige bekommen indexSequence
/makeIndexSequence
koppeln Sie 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 guter Weg, um zu verstehen, wie es funktioniert, darin besteht, einem praktischen Beispiel zu folgen.
Wir können Punkt für Punkt sehen, wie makeIndexSequence<3>
werden index_sequenxe<0, 1, 2>
.
-
Wir haben das
makeIndexSequence<3>
ist definiert alstypename indexSequenceHelper<3>::type
[N
is3
] -
indexSequenceHelper<3>
stimmen nur mit dem allgemeinen Fall überein, also erben vonindexSequenceHelper<2, 2>
[N
is3
andNext...
is empty] -
indexSequenceHelper<2, 2>
stimmen nur mit dem allgemeinen Fall überein, also erben vonindexSequenceHelper<1, 1, 2>
[N
is2
andNext...
is2
] -
indexSequenceHelper<1, 1, 2>
stimmen nur mit dem allgemeinen Fall überein, also erben vonindexSequenceHelper<0, 0, 1, 2>
[N
is1
andNext...
is1, 2
] -
indexSequenceHelper<0, 0, 1, 2>
Passen Sie beide Fälle (allgemeine und partielle Spezialisierung) an, damit die partielle Spezialisierung angewendet und definiert wirdtype = indexSequence<0, 1, 2>
[Next...
is0, 1, 2
]
Fazit: makeIndexSequence<3>
ist indexSequence<0, 1, 2>
.
Hoffe das hilft.
— BEARBEITEN —
Einige Klarstellungen:
-
std::index_sequence
undstd::make_index_sequence
sind ab C++14 verfügbar -
Mein Beispiel ist (hoffentlich) einfach zu verstehen, hat aber (wie von Aschepler gezeigt) die große Grenze, die eine lineare Implementierung ist; Ich meine: wenn nötig
index_sequence<0, 1, ... 999>
, verwendenmakeIndexSequence<1000>
Sie implementieren rekursiv 1000 verschiedeneindexSequenceHelper
; aber es gibt eine Rekursionsgrenze (Compiler von Compiler unterschiedlich), die kleiner als 1000 sein kann; Es gibt andere Algorithmen, die die Anzahl der Rekursionen begrenzen, aber komplizierter zu erklären sind.
-
Eine gute Antwort, aber ich würde hinzufügen, dass es einen Grund gibt, warum einige Implementierungen, die Sie möglicherweise finden, etwas komplizierter sind: Sie versuchen, die Anzahl und/oder Tiefe der Vorlageninstanziierungen zu reduzieren, da es eine Compiler-Grenze für die Tiefe rekursiver Instanziierungen geben kann , und die Reduzierung der Gesamtzahl könnte die Kompilierungszeiten verkürzen.
– Aschepler
5. April 18 um 12:56 Uhr
-
Danke schön! Das ist großartig. Genau das, wonach ich gesucht habe.
– Benutzer3613174
5. April 18 um 13:04 Uhr
-
@aschepler – Ich weiß, ich weiß … Normalerweise verwende ich (in C ++ 11) eine andere Implementierung mit logarithmischer Komplexität. Aber meine Absicht war es, es einfach zu verstehen. Vielleicht versuche ich diesen Punkt etwas besser zu erklären…
– max66
5. April 18 um 13:13 Uhr
-
@ max66 Ja, diese einfache Erklärung ist definitiv besser für die Frage geeignet. Ich schlage nur ein paar zusätzliche Informationen vor.
– Aschepler
5. April 18 um 13:20 Uhr
-
@ user3613174 – ein paar Klarstellungen hinzugefügt (siehe auch den Kommentar des Ascheplers).
– max66
5. April 18 um 13:36 Uhr
Papageien
Der Vollständigkeit halber füge ich eine modernere Implementierung von hinzu std::make_index_sequence
, verwenden if constexpr
und auto
, die die Template-Programmierung viel mehr zur “normalen” 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 empfehle dringend, diesen Stil der Vorlagenprogrammierung zu verwenden, da er einfacher zu begründen ist.
-
Interessant… ja, mit
if constexpr
einfacher werden. Aber das solltest du präzisierenif constexpr
ist ab C++17 verfügbar.– max66
5. April 18 um 13:25 Uhr
Benutzer1095108
Damit es nicht vergessen wird:
template <std::size_t N, std::size_t ...I>
constexpr auto make_index_sequence_impl() noexcept
{
if constexpr (!N)
{
return std::index_sequence<I...>();
}
else if constexpr (!sizeof...(I))
{
return make_index_sequence_impl<N - 1, 0>();
}
else if constexpr (N >= sizeof...(I))
{
return make_index_sequence_impl<N - sizeof...(I), I..., sizeof...(I) + I...>();
}
else
{
return []<auto ...J>(std::index_sequence<J...>) noexcept
{
return std::index_sequence<I..., sizeof...(I) + J...>();
}(make_index_sequence_impl<N>()); // index concatenation
}
}
template <size_t N>
using make_index_sequence = decltype(make_index_sequence_impl<N>());
.