2010-02-25 20 views
6

zasadzie robię co następuje:Jak parametryzować w kierunku iteratora?

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); 
    } 
} 

Zastanawiam się, czy istnieje sposób, aby napisać to samo w C++ 03 z tylko jednego połączenia do przetwarzania (I), w jakiś sposób parametryzacji w celu przetwarzania? Tak (co oczywiście nie działa nawet w C++ 0x ponieważ begin() i rbegin() zwracają różne rodzaje):

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); 
    } 

Odpowiedz

5

Co chcesz, możesz zaimplementować za pomocą Boost.Variant.

Chodzi o to, aby zdefiniować nowy typ iteratora, który przechowuje wariant (Pomyśl o tym jak związek C na sterydach) zawierający albo do przodu lub do tyłu 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); 
    }  
}; 

Jest to podobne do Adobe's any iterator ale znacznie mniej ogólne, co oznacza, że ​​nie ma praktycznie żadnego obciążenia w porównaniu do zwykłego iteratora.

Jak widać w powyższym kodzie, cała logika jest delegowana do odwiedzających statycznych które definiują następująco:

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 
    } 
}; 

To była skomplikowana część. Ale wciąż mamy zrobić to bardziej wygodne w użyciu, dla którego definiujemy funkcję pomocnika, który opiera się na funkcjonalności dostarczanej przez bibliotekę Boost.Range:

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)); 
} 

I to wszystko. Teraz można napisać:

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; 
} 

która drukuje:

1 2 
2 1 

Działa to z const pojemnikach, a także, choć nie wykazały, że możliwości w funkcji main.

Inną miłą rzeczą, która wynika z użycia Boost.Range, jest to, że działa z tablicami po wyjęciu z pudełka. Więc można to zrobić:

int items[] = { 1, 2 }; 

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

zbyt zachować to krótka odpowiedź Zostawiłam kilka rzeczy ZAIMPLEMENTOWANE (ale oni wszyscy boilerplate, nie wymagając nowych użytkowników):

  • operator postfix przyrost
  • operatora =
  • The -!> operator

Oto all the code in Codepad. Ze względu na zasadę "Traktuj ostrzeżenia jako błędy" Codepad nie połknie jej, ale zarówno VS2008, jak i GCC 4.4 skompilują ją poprawnie na moim lokalnym komputerze.

UPDATE

Zrobiłem kilka testów i widocznie boost::variant ma wprowadzić pewne obciążenie środowiska wykonawczego: a BOOST_FOREACH -na pętla jak ten w funkcji main działa około 4 razy wolniej (gdy kompilowany w trybie uwalniania) niż wersja równoważna za pomocą zwykłego iteratora. Interesujące byłoby sprawdzenie, czy jest to najlepsze, czy najgorsze, niż koszty ogólne wprowadzone przez firmę Adobe any_iterator.

+0

Wow, to o wiele więcej, niż się spodziewałem;) Zastanawiam się, czy rozwiązanie z dobrym "unii" byłoby możliwe. Ale już myślę, że posiadanie ekstra, jeśli jest lepsze niż wprowadzenie tego skomplikowanego kodu. Byłoby warto, gdybym robił takie rzeczy ciągle, ale to tylko w kilku miejscach. Dzięki i tak! –

+1

@Alex - Nie sądzę, że można tu użyć związków, sprawdź: http://stackoverflow.com/questions/943267/is-it-a-good-practice-to-use-unions-in-c/ 943611 # 943611 – Manuel

1

Otóż oczywiste jest, aby klasy, która obsługuje tę logikę sytuacji, poprzez przechowywanie flagi lub użycie polimorfizmu. Jednak w najlepszym wypadku będzie to "ukrywanie" instrukcji if.

+0

Czy mógłbyś naszkicować logikę radzenia sobie z tą sytuacją bez używania makr? Tak jak ja to widzę, wewnątrz tej klasy wciąż będą dwa (pośrednie) połączenia do przetwarzania w dwóch różnych pętlach. –

+0

Możesz użyć szablonów do implementacji (jedna pętla, dwa scenariusze). Nie widzę sposobu, w jaki można w * środowisku wykonawczym * określić typy iteratorów do użycia - nawet z makrami - bez konieczności kodowania logiki dla obu iteratorów. – UncleBens

Powiązane problemy