Hacer que los bucles for basados en rangos de C ++ 11 sean un poco más útiles

C ++ 11 es genial. Probablemente una de las características más hermosas (en mi opinión) es el llamado rango basado en bucle. En lugar de

for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; } 

o

for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; } 

o simplificado con C ++ 11 auto:

for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; } 

Podemos decir esto:

for ( auto& entry : range ) { // do something with entry. } 

Esto es muy expresivo: hablamos sobre la entrada, en lugar de la i ^ ésima posición en el rango o el iterador que apunta a una entrada. Sin embargo, esta sintaxis carece de la capacidad de tener subrangos (por ejemplo, ignorando la última entrada). En una serie de pequeñas estructuras / métodos de ayuda, quiero agregar esta funcionalidad de una manera limpia.

Hasta aquí mi motivación 😉 Ahora, para el trato real.

En esta publicación, abordo las condiciones de las entradas de range. Básicamente, el código auxiliar debería realizar el equivalente a

for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } } 

pero sin ese nivel de sangría.

Aquí está el código para ConditionalRange:

template <typename Range, typename Runnable> ConditionalRange<Range, Runnable> makeConditionalRange(Range& range, Runnable&& condition) { static_assert(std::is_same<decltype(condition(*std::declval<Range>().begin())), bool>::value, "Condition must return a boolean value."); return ConditionalRange<Range, Runnable>(range, std::forward<Runnable>(condition)); } template <typename Range, typename Runnable> struct ConditionalRange { public: friend ConditionalRange makeConditionalRange<>(Range&, Runnable&&); public: using iterator_type = ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>; iterator_type begin() const { auto b = range.begin(); while ( b != range.end() && !condition(*b) ) { ++b; } return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(b, range.end(), condition); } iterator_type end() const { return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(range.end(), range.end(), condition); } private: ConditionalRange(Range& range_, Runnable&& condition_) : range(range_), condition(std::forward<Runnable>(condition_)) { } private: Range& range; Runnable condition; }; 

Esta es la estructura auxiliar para el iterador:

template <typename Iterator, typename Runnable> struct ConditionalIterator { private: Iterator iterator; Iterator end; const Runnable& condition; public: ConditionalIterator(Iterator b, Iterator e, const Runnable& r) : iterator(b), end(e), condition(r) { } auto operator*() -> decltype(*iterator) { return *iterator; } ConditionalIterator& operator++() { do { ++iterator; } while ( iterator != end && !condition(*iterator) ); return *this; } bool operator==(const ConditionalIterator& second) { return iterator == second.iterator; } bool operator!=(const ConditionalIterator& second) { return !(*this == second); } }; 

El uso previsto es:

std::vector<int> ns{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for ( const auto& n : makeConditionalRange(ns, [](int a) { return a % 2 == 0; }) ) { std::cout << n << " "; } std::cout << "\n"; 

Demostración: haga clic aquí

¿Qué Cuáles son las debilidades de mi enfoque? ¿Con qué tipo de argumentos falla, aunque no debería hacerlo en el mundo perfecto?

Comentarios

  • No veo debilidades, pero Creo que debería considerar cambiar el nombre de makeConditionalRange con algo que se centre en la apariencia del código del cliente, no en " hacer un rango condicional ". Por eso, significa que el código del cliente debería verse así: for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
  • Por lo tanto, la intención aquí difiere significativamente de un Boost filter_iterator ?
  • @JerryCoffin No, no ' t. Simplemente tengo ' t busqué en Boost 😉
  • La prueba más común para iteradores operator!= optimiza esto sobre operator==
  • @stefan: Para ser franco, ' es una tontería. Es como decir que no ' t suele comprar un coche de los fabricantes estándar porque quiero construir yo mismo uno que se escurra de algas para garantizar que no contamine. El problema es que su conjunto de bibliotecas tendrá infinitamente más errores que impulso porque solo tiene un par de ojos mirándolas. Boost tiene miles de personas que verifican y validan la corrección del código y brindan comentarios para asegurarse de que se usen correctamente los modismos correctos. Leer: stackoverflow.com/a/149305/14065

Respuesta

Puede usar llaves para regresar de sus funciones. De esa manera, no tendrá que repetir tipos largos y engorrosos (DRY, tanto como sea posible). Por ejemplo, ConditionalRange::end:

iterator_type end() const { return { range.end(), range.end(), condition }; } 

Además, probablemente quieras que tu función también acepte matrices de estilo C. Por lo tanto, debes usar std::begin y std::end en lugar de llamar a las funciones miembro:

iterator_type end() const { return { std::end(range), std::end(range), condition }; } 

Con eso (en todas partes de su código) y algunos cambios triviales, su código también funcionará para C- matrices de estilo (probado aquí y funciona bien).

Nombrar tu función

El nombre makeConditionalRange es bastante larga. La función que creaste ya existe en otras bibliotecas y lenguajes de programación (me viene a la mente Python) con el nombre filter. Deberías considerar cambiando su nombre para que muchos usuarios reconozcan el nombre a primera vista (además, será más corto).

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *