2017-09-22 2 views
3

In meinem Code stoße ich regelmäßig auf Situationen, in denen ich abhängig von einer Laufzeitbedingung in direkter oder umgekehrter Reihenfolge über einen Bereich iterieren möchte. Dies resultiert typischerweise in Code wie der folgendeImplementierung eines Boost-Bereichsadapters reversed_if

if (reverse) { 
    using boost::adaptors::reversed; 
    for (auto const & x : range | reversed) do_stuff(x); 
} else { 
    for (auto const & x : range) do_stuff(x); 
} 

oder

std::vector<Foo> v(range.begin(), range.end()); 
if (reverse) boost::range::reverse(v); 
for (auto const & x : v) do_stuff(x); 

die Code-Duplizierung enthält (erste) oder ineffizient ist (zweite).

Ich habe so viele Male über einen hypothetischen Boost-Bereich Adapter denke, dass bedingt umkehren würde eine Reihe, dass ich

using boost::adaptors::reversed_if; 
for (auto const & x : range | reversed_if(reverse)) do_stuff(x); 

schreiben konnte ich es selbst umsetzen kann (von here Start), aber ich bin nicht sicher, wie es weitergeht. Um Runtime-Bedingungen zu unterstützen, muss ich leider bei jeder Iteration einen Booleschen Wert überprüfen, um die Iterationsrichtung zu bestimmen, oder Virtualität verwenden, um zu Iterationscode zu gelangen. Ist dies der Grund, warum dies nicht in Boost Bereich Adapter zur Verfügung gestellt wird?

Eine alternative Lösung?

Antwort

1

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.

+0

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? –

+1

@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

+0

@ChristopheFuzier Beachten Sie auch, dass 'magic_switch' basierend auf' variants' funktionieren kann. – Yakk

Verwandte Themen