2010-02-25 4 views
6

Im Grunde mache ich folgendes:Wie parametriere ich auf Iterator-Richtung?

std::set<int> indices; 
// ..fill indices 

if (flag) 
{ 
    // we need to process in ascending order 
    BOOST_FOREACH (int i, indices) 
    { 
     process(i); 
    } 
} 
else 
{ 
    // we need to process in descending order 
    BOOST_REVERSE_FOREACH (int i, indices) 
    { 
     process(i); 
    } 
} 

Ich frage mich, ob es einen Weg gibt, die gleiche Sache in C++ 03 mit nur einem Anruf Verfahren (i), irgendwie Parametrisierung über die Verarbeitung, um zu schreiben? Wie dies (die offensichtlich nicht einmal in C++ 0x funktioniert, weil beginnen() und rbegin() verschiedene Arten zurück):

auto iter = flag ? indices.begin() : indices.rbegin(); 
    auto end = flag ? indices.end() : indices.rend(); 

    BOOST_FOREACH (int i, std::make_pair(iter, end)) 
    { 
     process(i); 
    } 

Antwort

5

Was Sie wollen, kann mit Boost.Variant implementiert werden.

Die Idee ist, eine neue Art von Iterator zu definieren, die eine Variante speichert (denken Sie daran, wie eine C Vereinigung auf Steroide), die entweder eine Vorwärts- oder Rückwärts Iterator:

template<class InputRange> 
struct any_dir_iterator 
: std::iterator_traits<typename boost::range_iterator<InputRange>::type> { 

    typedef typename boost::range_iterator<InputRange>::type forward_iterator; 
    typedef typename 
     boost::range_reverse_iterator<InputRange>::type reverse_iterator; 

    typedef boost::variant<forward_iterator, reverse_iterator> iterator_type; 

    iterator_type current_it, end_it; 

    any_dir_iterator(InputRange & input_range, 
        bool fwd = true, 
        bool end = false) 
    { 
     end_it = fwd ? iterator_type(boost::end(input_range)) 
        : iterator_type(boost::rend(input_range)); 

     if(end) 
      current_it = end_it; 
     else 
      current_it = fwd ? iterator_type(boost::begin(input_range)) 
          : iterator_type(boost::rbegin(input_range)); 
    } 

    reference operator*() const { 
     return boost::apply_visitor(dereference_visitor<any_dir_iterator>(), 
            current_it); 
    } 

    any_dir_iterator & operator++() { 
     boost::apply_visitor(increment_visitor<any_dir_iterator>(), 
          current_it); 
     return *this; 
    } 

    bool operator==(any_dir_iterator const & rhs) { 
     return boost::apply_visitor(equals_visitor<any_dir_iterator>(), 
            current_it, rhs.current_it); 
    }  
}; 

Dies ist ähnlich Adobe's any iterator aber viel weniger allgemein, was bedeutet, dass es im Vergleich zu einem einfachen Iterator praktisch keinen Leistungsaufwand hat.

Wie Sie oben im Code sehen können, die gesamte Logik wird auf statische Besucher delegiert, die wir wie folgt definieren:

template<class AnyDirIterator> 
struct dereference_visitor 
: boost::static_visitor<typename AnyDirIterator::iterator_type> { 

    typedef typename AnyDirIterator::reference result_type; 

    template<class FwdOrRevIterator> 
    result_type operator()(FwdOrRevIterator const & it) const { 
     return *it; 
    } 
}; 

template<class AnyDirIterator> 
struct increment_visitor 
: boost::static_visitor<typename AnyDirIterator::iterator_type> { 

    typedef void result_type; 

    template<class FwdOrRevIterator> 
    result_type operator()(FwdOrRevIterator & it) const { 
     ++it; 
    } 
}; 

template<class AnyDirIterator> 
struct equals_visitor 
: boost::static_visitor<typename AnyDirIterator::iterator_type> 
{ 
    typedef bool result_type; 

    template <typename FwdOrRevIterator> 
    bool operator()(FwdOrRevIterator const & lhs, 
        FwdOrRevIterator const & rhs) const { 
     return lhs == rhs; 
    } 

    template <typename T, typename U> 
    bool operator()(const T &, const U &) const { 
     return false; // comparing fwd to rev or vice-versa 
    } 
}; 

Das ist der schwierige Teil war. Aber wir müssen noch bequemer machen diese zu verwenden, für die definieren wir eine Hilfefunktion, die auf die Funktionalität beruht von der Boost.Range Bibliothek zur Verfügung gestellt:

template<class InputRange> 
boost::iterator_range<any_dir_iterator<InputRange> > 
make_any_dir_range(InputRange & range, bool forward=true) { 
    typedef any_dir_iterator<InputRange> iterator; 
    return boost::make_iterator_range(iterator(range, forward), 
             iterator(range, forward, true)); 
} 

Und das ist alles. Jetzt können Sie schreiben:

int main() { 

    int items[] = { 1, 2 }; 
    typedef std::vector<int> container_type; 
    container_type container(items, items + sizeof(items)/sizeof(items[0])); 

    BOOST_FOREACH(int i, make_any_dir_range(container, true)) 
     std::cout << i << " "; 

    std::cout << "\n"; 
    BOOST_FOREACH(int i, make_any_dir_range(container, false)) 
     std::cout << i << " "; 

    return 0; 
} 

Welche druckt:

1 2 
2 1 

Das mit konst Containern funktioniert auch, obwohl ich diese Möglichkeit nicht in der main Funktion gezeigt.

Eine weitere nette Sache, die sich aus der Verwendung von Boost.Range ergibt, ist, dass dies mit Arrays aus der Box funktioniert. So können Sie dies tun:

int items[] = { 1, 2 }; 

BOOST_FOREACH(int i, make_any_dir_range(items, true)) // Prints "1 2" 
    std::cout << i << " "; 

Zu dieser Antwort kurz halten wir ein paar Dinge verlassen haben nicht umgesetzt (aber sie sind alle vorformulierten, ohne eigenen neuen Besucher):

  • Der Postfix Inkrementoperator
  • Der Operator =
  • Die -> Operator

Hier all the code in Codepad. Aufgrund der Richtlinie "Behandle Warnungen als Fehler" wird Codepad es nicht schlucken, aber sowohl VS2008 als auch GCC 4.4 kompilieren es in meinem lokalen Rechner.

UPDATE

ich einige Tests gemacht habe und offenbar boost::variant einig Laufzeit-Overhead nicht vorstellen: eine BOOST_FOREACH -basierte Schleife, wie die in der main Funktion läuft über 4-mal langsamer (wenn im Release-Modus kompiliert) als die äquivalente Version mit einem einfachen Iterator. Es wäre interessant zu prüfen, ob dies am besten oder am schlechtesten ist als der Overhead, der von Adobe any_iterator eingeführt wurde.

+0

Wow, das ist viel mehr als ich erwartet habe;) Ich frage mich, ob die Lösung mit einer guten 'ol Vereinigung möglich wäre. Aber ich denke schon, dass es besser ist, einen zusätzlichen Code zu haben, als diesen komplizierten Code einzuführen. Es könnte sich lohnen, wenn ich so etwas ständig mache, aber es ist nur an ein paar Orten. Danke trotzdem! –

+1

@Alex - Ich glaube nicht, dass Gewerkschaften hier verwendet werden können, überprüfen Sie dies: http://StackOverflow.com/Questions/943267/isit-a-Good-Practice-To-use-Unions-in-C/ 943611 # 943611 – Manuel

1

Nun, die offensichtlichste ist, um eine Klasse zu machen, die die Logik dieser Griffe Situation, entweder durch Speichern eines Flags oder durch Polymorphie. Es würde jedoch bestenfalls die if Aussage "verstecken".

+0

Könnten Sie bitte die Logik der Handhabung dieser Situation skizzieren, ohne Makros zu verwenden? So wie ich es sehe, werden innerhalb dieser Klasse immer noch zwei (indirekte) Aufrufe in zwei verschiedenen Schleifen ausgeführt. –

+0

Sie könnten Vorlagen für die Implementierung verwenden (eine Schleife, zwei Szenarien). Ich sehe nicht, wie man zur * Laufzeit * die zu verwendenden Iterator-Typen bestimmen kann - auch mit Makros -, ohne die Logik für beide Iteratoren zu codieren. – UncleBens