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
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).
for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
filter_iterator
?operator!=
optimiza esto sobreoperator==