2010-02-25 9 views
6

Básicamente estoy haciendo lo siguiente:¿Cómo parametrizar en la dirección del iterador?

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

Me pregunto si hay una manera de escribir lo mismo en C++ 03 con sólo una llamada a procedimiento (i), de alguna manera parametrización en el orden de procesamiento? Como esta (que obviamente no funciona incluso en C++ 0x porque begin() y rbegin() devuelven diferentes tipos):

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

Respuesta

5

Lo que desee se puede implementar con Boost.Variant.

La idea es definir un nuevo tipo de iterador que almacena una variante (pensar en él como una unión C en esteroides) que contiene ya sea un avance o retroceso iterador:

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

Esto es similar a Adobe's any iterator pero mucho menos general, lo que significa que prácticamente no tendrá sobrecarga de rendimiento cuando se compara con un iterador simple.

Como se puede ver en el código anterior, toda la lógica se delega a los visitantes estáticas que definimos de la siguiente manera:

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

Esa fue la parte difícil. Pero todavía tenemos que hacer esto más cómodo de usar, para el que se define una función auxiliar que se basa en la funcionalidad proporcionada por la biblioteca 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)); 
} 

y eso es todo. Ahora se puede escribir:

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

que imprime:

1 2 
2 1 

Esto funciona con recipientes const así, aunque no he demostrado que la posibilidad en la función main.

Otra cosa agradable que resulta del uso de Boost.Range es que esto funciona con arreglos listos para usar. Así que usted puede hacer esto:

int items[] = { 1, 2 }; 

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

Demasiado mantener esta respuesta corta que he dejado algunas cosas sin aplicarse (pero todos son repetitivo, que no requieren nuevos visitantes):

  • El operador de incremento de sufijo
  • El operador =
  • El -> operador

Aquí es all the code in Codepad. Debido a la política de "tratar advertencias como errores", Codepad no se lo tragará, pero tanto VS2008 como GCC 4.4 lo compilan correctamente en mi máquina local.

ACTUALIZACIÓN

he hecho algunas pruebas y aparentemente boost::variant sí introduce algo de sobrecarga de tiempo de ejecución: a BOOST_FOREACH bucle basado como el de la función de main se ejecuta alrededor de 4 veces más lentas (cuando se compila en modo de lanzamiento) que la versión equivalente usando un iterador simple. Sería interesante comprobar si esto es mejor o peor que los gastos generales introducidos por el any_iterator de Adobe.

+0

Guau, esto es mucho más de lo que esperaba;) Me pregunto si la solución con una buena unión sería posible. Pero ya creo que tener un extra si es mejor que presentar este código complicado. Podría valer la pena si estuviera haciendo este tipo de cosas constantemente, pero solo en un par de lugares. ¡Gracias de cualquier manera! –

+1

@Alex - No creo que las uniones se puedan usar aquí, verifique esto: http://stackoverflow.com/questions/943267/is-it-a-good-practice-to-use-unions-in-c/ 943611 # 943611 – Manuel

1

bien la más obvia es hacer una clase que maneja la lógica de este situación, ya sea almacenando una bandera o usando polimorfismo. Sin embargo, en el mejor de los casos estaría "ocultando" la declaración if.

+0

¿Podría describir la lógica de manejar esta situación sin usar macros en bruto? La forma en que lo veo, dentro de esta clase todavía habrá dos llamadas (indirectas) para procesar en dos bucles diferentes. –

+0

Puede usar plantillas para la implementación (un ciclo, dos escenarios). No veo cómo puede en * runtime * determinar los tipos de iteradores a usar, incluso con macros, sin haber codificado la lógica para ambos iteradores. – UncleBens

Cuestiones relacionadas