Wenn Sie die Laufzeitprüfung bei jedem Schritt vermeiden möchten, müssen Sie den Laufzeitwert in einen Kompilierungszeitwert außerhalb der Schleifenstruktur konvertieren.
In diesem Fall möchten wir, dass der Bereich, den wir durchlaufen, variiert, während der Körper dies nicht tut.
Die einfache Weg, dies zu tun ist, ein Lambda für den Körper zu schreiben, dann haben Sie einen Schalter, um auszuwählen, welche Schleife zu wählen.
auto do_stuff = [&](auto&& elem){ /* code */ };
if (reverse) {
using boost::adaptors::reversed;
for (auto const & x : range | reversed) do_stuff(x);
} else {
for (auto const & x : range) do_stuff(x);
}
haben wir die Laufzeit Versendung aus der Schleife erfolgt, zwei verschiedene Schleifen mit statischen Typ-Informationen zu schaffen, wie sie Schleife.
Wir können einen Adapter wie diese machen:
magic_switch
(reverse)
(range, range|reversed)
(
[&](auto&& range){
for (auto const& x : decltype(range)(range)) {
do_stuff(x);
}
}
);
wo magic_switch
einen Index (std::size_t
) als erstes Argument nimmt. Es gibt ein Lambda zurück, das eine Liste von Argumenten enthält. Sie gibt ein Lambda zurück, das ein Lambda annimmt und ihm das Argument aus der 2. Liste, wie vom Index des ersten Arguments bestimmt, in diese Liste übergibt.
inline auto magic_switch(std::size_t I) {
return [I](auto&&...options) {
return [I, &](auto&& f)->decltype(auto) {
using fptr = void(*)(void const volatile* op, decltype(f));
static const fptr table[] = {
+[](void const volatile* op_in, decltype(f) f) {
auto* option = static_cast<std::decay_t<decltype(options)>*>(op_in);
decltype(f)(f)(decltype(options)(*option));
}...
};
const volatile void* ptrs[] = {std::addressof(options)...};
if (I >= sizeof...(options)) I = sizeof...(options)-1;
if (I == -1) return;
table[I](ptrs[I], decltype(f)(f));
};
};
}
ist eine Skizze bei einer Implementierung (es enthält fast sicher Build-Fehler).
Der schwierige Teil ist, dass "type flow" (um einen Begriff zu prägen) nicht so geht, wie Sie es normalerweise wollen. Daher bin ich gezwungen, den Continuation-Passing-Stil grundsätzlich zu verwenden.
Beachten Sie, dass viele Compiler mit einer Pack-Erweiterung, die einen gesamten Lambda enthält, nicht zufrieden sind. Eine Hilfsfunktion, die die Funktionszeiger zurückgibt, kann geschrieben werden:
template<class F>
using f_ptr = void(*)(const volatile void*, F&&);
template<class Option, class F>
f_ptr<F> get_f_ptr() {
return +[](void const volatile* op_in, F&& f) {
auto* option = static_cast<std::decay_t<Option>*>(op_in);
std::forward<F>(f)(std::forward<Option>(*option));
};
}
dann ersetzen Sie die Tabelle mit:
static const fptr table[] = {
get_fptr<decltype(options), decltype(f)>()...
};
auf diesem Compiler.
Wow! Vielen Dank! Ich bekomme die Idee, aber ich werde versuchen, die Implementierungsdetails Ihres magischen Schalters zu verstehen. Was ist der Sinn von 'decltype (range) (range)'? Ist das eine Besetzung und warum wird sie benötigt? –
@ChristopheFuzier Für eine 'auto &&' -Referenz ist das eine perfekte Weiterleitung. Lies es als "Bereich als den Typ, für den es erklärt wurde". Es entspricht »std :: forward (range)«, aber halb so lang, solange »range« vom Typ »auto &&« oder »T &&« (Weiterleitungsreferenz) ist. Wenn 'range' ein Werttyp ist (' auto' oder 'T'), funktioniert es nicht. –
Yakk
@ChristopheFuzier Beachten Sie auch, dass 'magic_switch' basierend auf' variants' funktionieren kann. – Yakk