Iterador de preprocesamiento -- ++ campo con c++14 campo con iterator camp codereview Relacionados El problema

Preprocessing iterator


7
vote

problema

Español

Después de ajustar mucho la semántica, terminó siendo una versión de objeto muy pobre de std::transform<>() que se puede usar con otros algoritmos STD.

Básicamente, el iterador envuelve un iterador proporcionado, creando un iterador de salida y aplica el Functor suministrado cuando está deteriorado y asignado a. La advertencia es que los usuarios deberán identificar el tipo de parámetro, lo que puede ser propenso a errores, pero podría abrir oportunidades para otros usos. El tipo de entrada no debe ser calificado por CV, ya que tiene sobrecargas tanto para la referencia Const Lvalue como para la referencia de Rvalue. Si el usuario desea tomar un parámetro por valor, deberían escribir otro funtor para eso.

  #ifndef SUNRISE_PREPROCESSING_ITERATOR_HPP #define SUNRISE_PREPROCESSING_ITERATOR_HPP  #include <iterator>  namespace shino {     template<typename Functor, typename InputType, typename Iterator>     class preprocessing_iterator :             public std::iterator<std::output_iterator_tag,                     InputType, void, void, void>     {         Functor functor;          Iterator iterator;     public:         preprocessing_iterator(const Functor &f, const Iterator &it) :                 functor(f),                 iterator(it)         {}          preprocessing_iterator(Functor &&f, Iterator &&it) :                 functor(f),                 iterator(it)         {}          class proxy         {             friend class preprocessing_iterator;              Iterator &iterator;             Functor &f;         public:             using value_type = InputType;              proxy &operator=(const value_type &value)             {                 *iterator = f(value);                 return *this;             }              proxy &operator=(value_type &&value)             {                 *iterator = f(value);                 return *this;             }          private:             proxy(Iterator &it, Functor &functor) :                     iterator(it),                     f(functor)             {}         };          proxy operator*()         {             return proxy(iterator, functor);         }          preprocessing_iterator &operator++()         {             ++iterator;             return *this;         }          preprocessing_iterator operator++(int)         {             auto copy = *this;             ++iterator; //might exhibit different behavior sometimes             return copy;         }          const Iterator& internal_iterator() const         {             return iterator;         }          const Functor& internal_functor() const         {             return functor;         }          void swap(preprocessing_iterator& other)         {             using std::swap;             swap(other.functor, functor);             swap(other.iterator, iterator);         }     };      template<typename Functor, typename InputType, typename Iterator>     bool operator==(const preprocessing_iterator<Functor, InputType, Iterator>& lhs,                    const preprocessing_iterator<Functor, InputType, Iterator>& rhs)     {         return lhs.internal_iterator() == rhs.internal_iterator();     };      template <typename Functor, typename InputType, typename Iterator>     bool operator!=(const preprocessing_iterator<Functor, InputType, Iterator>& lhs,                     const preprocessing_iterator<Functor, InputType, Iterator>& rhs)     {         return !(lhs == rhs);     }; }  namespace std {     template <typename Functor, typename InputType, typename Iterator>     void swap(shino::preprocessing_iterator<Functor, InputType, Iterator>& lhs,               shino::preprocessing_iterator<Functor, InputType, Iterator>& rhs)     {         lhs.swap(rhs);     }; }    #endif //SUNRISE_PREPROCESSING_ITERATOR_HPP   

Uso:

  #include <vector> #include <string> #include <algorithm> #include <iostream>  void convert_strings_to_ints() {     std::vector<std::string> v{"23", "25", "27"}; //just random numbers     std::vector<int> output(v.size());      auto string_to_int = [](const std::string& x)     {         return std::stoi(x);     };      shino::preprocessing_iterator<decltype(string_to_int),             std::string, std::vector<int>::iterator> conversion_iterator(string_to_int, output.begin());      std::copy(v.begin(), v.end(), conversion_iterator);      std::cout << "Output after converting v to vector of ints ";      for (const auto& element: output)     {         std::cout << element << ' ';     } }  int main() {     convert_strings_to_ints(); }   

Como se puede ver, el mejor caso de uso es escribir adaptadores. He intentado bajar la sobrecarga tanto como sea posible, pero si 99887766555443333 no es liviano para copiar, puede convertirse en un problema si los iteradores se copian demasiado.

Mis preocupaciones específicas son:

  • fácil de usar. Este es el problema principal.

  • lugares oscuros que deben documentarse. Desde la primera vista de mi familiar nada parece extraño o ambiguo.

  • Cualquier lugar donde podría causar accidentalmente muy lento.

Original en ingles

After tweaking semantics a lot, it ended up being a very poor object version of std::transform<>() that can be used with other std algorithms.

Basically the iterator wraps a provided iterator, creating an output iterator and applies supplied functor when dereferenced and assigned to. The caveat is that users will need to identify parameter type themselves, which can be error prone, but might open opportunities for other usages. Input type should not be cv qualified, because it has overloads for both const lvalue reference and rvalue reference. If the user wishes to take parameter by value, they would need to write another functor for that.

#ifndef SUNRISE_PREPROCESSING_ITERATOR_HPP #define SUNRISE_PREPROCESSING_ITERATOR_HPP  #include <iterator>  namespace shino {     template<typename Functor, typename InputType, typename Iterator>     class preprocessing_iterator :             public std::iterator<std::output_iterator_tag,                     InputType, void, void, void>     {         Functor functor;          Iterator iterator;     public:         preprocessing_iterator(const Functor &f, const Iterator &it) :                 functor(f),                 iterator(it)         {}          preprocessing_iterator(Functor &&f, Iterator &&it) :                 functor(f),                 iterator(it)         {}          class proxy         {             friend class preprocessing_iterator;              Iterator &iterator;             Functor &f;         public:             using value_type = InputType;              proxy &operator=(const value_type &value)             {                 *iterator = f(value);                 return *this;             }              proxy &operator=(value_type &&value)             {                 *iterator = f(value);                 return *this;             }          private:             proxy(Iterator &it, Functor &functor) :                     iterator(it),                     f(functor)             {}         };          proxy operator*()         {             return proxy(iterator, functor);         }          preprocessing_iterator &operator++()         {             ++iterator;             return *this;         }          preprocessing_iterator operator++(int)         {             auto copy = *this;             ++iterator; //might exhibit different behavior sometimes             return copy;         }          const Iterator& internal_iterator() const         {             return iterator;         }          const Functor& internal_functor() const         {             return functor;         }          void swap(preprocessing_iterator& other)         {             using std::swap;             swap(other.functor, functor);             swap(other.iterator, iterator);         }     };      template<typename Functor, typename InputType, typename Iterator>     bool operator==(const preprocessing_iterator<Functor, InputType, Iterator>& lhs,                    const preprocessing_iterator<Functor, InputType, Iterator>& rhs)     {         return lhs.internal_iterator() == rhs.internal_iterator();     };      template <typename Functor, typename InputType, typename Iterator>     bool operator!=(const preprocessing_iterator<Functor, InputType, Iterator>& lhs,                     const preprocessing_iterator<Functor, InputType, Iterator>& rhs)     {         return !(lhs == rhs);     }; }  namespace std {     template <typename Functor, typename InputType, typename Iterator>     void swap(shino::preprocessing_iterator<Functor, InputType, Iterator>& lhs,               shino::preprocessing_iterator<Functor, InputType, Iterator>& rhs)     {         lhs.swap(rhs);     }; }    #endif //SUNRISE_PREPROCESSING_ITERATOR_HPP 

Usage:

#include <vector> #include <string> #include <algorithm> #include <iostream>  void convert_strings_to_ints() {     std::vector<std::string> v{"23", "25", "27"}; //just random numbers     std::vector<int> output(v.size());      auto string_to_int = [](const std::string& x)     {         return std::stoi(x);     };      shino::preprocessing_iterator<decltype(string_to_int),             std::string, std::vector<int>::iterator> conversion_iterator(string_to_int, output.begin());      std::copy(v.begin(), v.end(), conversion_iterator);      std::cout << "Output after converting v to vector of ints\n";      for (const auto& element: output)     {         std::cout << element << '\n';     } }  int main() {     convert_strings_to_ints(); } 

As it can be seen, the best usage case is writing adapters. I've tried to lower the overhead as much as possible, but if Functor is not lightweight to copy, it might become a problem if iterators are copied too much.

My specific concerns are:

  • Easy of use. This is the main issue.

  • Obscure places that should be documented. From the first sight of my familiar nothing seems strange or ambiguous.

  • Any place where I could accidentally cause very big slow down.

        

Lista de respuestas

6
 
vote
vote
La mejor respuesta
 

Si entiendo correctamente, su objetivo es hacer este código de ejemplo:

  shino::preprocessing_iterator<decltype(string_to_int), std::string, std::vector<int>::iterator>     conversion_iterator(string_to_int, output.begin());  std::copy(v.begin(), v.end(), conversion_iterator);   

Tener un efecto equivalente a este código actual:

  std::transform(v.begin(), v.end(), output.begin(), string_to_int);   

(cuando lo digo de esa manera, parece casi un poco tonto, ¿no es así?))


La primera mejora de la usabilidad que haría es notar que su tipo de iterador está fundamentalmente relacionado con std::transform (en realidad ya notaste esto) y cambia su nombre a shino::transform_iterator .

También se ve análogo a std::back_insert_iterator , en el sentido de que tiene referencias a algunos otros objetos y su constructor es realmente incómodo para escribir a mano. Así que seguiría la práctica de la STL aquí creando una función de ayuda. El nombre lógico sería shino::make_transform_iterator , pero en su lugar podría seguir la práctica tradicional (cf. 9988776655544336 ) y llámalo simplemente std::transformer . Parece esto:

  template<class InputType, class OutputIterator, class F> auto transformer(F f, OutputIterator dest)     -> transform_iterator<F, InputType, OutputIterator> {     return {std::move(f), std::move(dest)}; }   

Así que ahora se ve su código de ejemplo de destino:

  auto conversion_iterator = shino::transformer<std::string>(string_to_int, output.begin()); std::copy(v.begin(), v.end(), conversion_iterator);   

¡Eso es una mejora!


A continuación, me doy cuenta de que std::transform(v.begin(), v.end(), output.begin(), string_to_int); 0 se siente fuera de lugar. En realidad, no estamos usando para nada. Así que intentaría deshacerme de él, cazando cada lugar que su código depende de std::transform(v.begin(), v.end(), output.begin(), string_to_int); 1 y reescribiéndolo a no use std::transform(v.begin(), v.end(), output.begin(), string_to_int); 2 .

por ejemplo, en lugar de

  std::transform(v.begin(), v.end(), output.begin(), string_to_int); 3  

Intentaría escribir simplemente

  std::transform(v.begin(), v.end(), output.begin(), string_to_int); 4  

Estaba un poco preocupado de que esto pudiera hacer lo incorrecto al encadenar std::transform(v.begin(), v.end(), output.begin(), string_to_int); 5 S juntos, pero mi primer caso de prueba trivial funcionó, así que voy a ignorar esa preocupación molestia. :) Ese caso de prueba fue:

  std::transform(v.begin(), v.end(), output.begin(), string_to_int); 6  

El otro lugar donde usa std::transform(v.begin(), v.end(), output.begin(), string_to_int); 7 está en su herencia de std::transform(v.begin(), v.end(), output.begin(), string_to_int); 8 . Afortunadamente, Herencia de std::transform(v.begin(), v.end(), output.begin(), string_to_int); 9 es no por más tiempo considerado à la modo ; De hecho, std::transform0 ¡En sí puede estar en desuso pronto! Entonces, lo que debe hacer es simplemente

  std::transform1  

Observe que std::transform2 te recomienda firmemente para poner el std::transform3 línea; y std::transform4 , std::transform5 std::transform6 también se convierte en std::transform7 por analogía a std::transform8 .

AVISO QUE ESTOS MIEMBROS DE TYPEDEFS deben ser std::transform9 . (libc ++ es feliz si son privados; pero libstdc ++ no.)


Por último, su especialización de shino::transform_iterator0 también ya no es à la modo . En lugar de interpretar su propio código en el espacio de nombres 9988777766554433331, debe proporcionar un 998877766655443332 que puede ser encontrado por adl .

su código es 100% correcto y razonable; Solo debe cambiar ese shino::transform_iterator4 a shino::transform_iterator5 , y estará en el negocio.

La forma esperada para los usuarios (como los algoritmos de STL) intercambiar dos shino::transform_iterator6 shino::transform_iterator77 y shino::transform_iterator38 es:

  shino::transform_iterator9  

No tengo un comentario en particular sobre la eficiencia o la sutil corrección de cómo su código (o mi código revisado) maneja el reenvío de funtores y demás. Probablemente, solo intente escribir algunas pruebas para eso (por ejemplo, ¿qué pasa si el Functor en cuestión sea solo para moverse?), Y corregir cualquier problema que encuentre, y luego continúe con mi vida. ;)


Aquí hay una caja de wandbox del código después de estos cambios.

 

If I understand correctly, your goal is to make this example code:

shino::preprocessing_iterator<decltype(string_to_int), std::string, std::vector<int>::iterator>     conversion_iterator(string_to_int, output.begin());  std::copy(v.begin(), v.end(), conversion_iterator); 

have an effect equivalent to this present-day code:

std::transform(v.begin(), v.end(), output.begin(), string_to_int); 

(When I say it that way, it seems almost a little silly, doesn't it? ;))


The first usability improvement I'd make is to notice that your iterator type is fundamentally related to std::transform (actually you already noticed this) and change its name to shino::transform_iterator.

It also looks analogous to std::back_insert_iterator, in the sense that it holds references to some other objects and its constructor is really awkward to type out by hand. So I'd follow the practice of the STL here by creating a helper function. The logical name would be shino::make_transform_iterator, but you could instead follow traditional practice (cf. back_inserter) and call it simply std::transformer. It looks like this:

template<class InputType, class OutputIterator, class F> auto transformer(F f, OutputIterator dest)     -> transform_iterator<F, InputType, OutputIterator> {     return {std::move(f), std::move(dest)}; } 

So now your target example code looks like this:

auto conversion_iterator = shino::transformer<std::string>(string_to_int, output.begin()); std::copy(v.begin(), v.end(), conversion_iterator); 

That's an improvement!


Next, I notice that class InputType feels out-of-place. We don't actually use it for anything. So I'd try to get rid of it, by hunting down each place that your code depends on InputType and rewriting it to not use InputType.

For example, instead of

proxy &operator=(const value_type &value) {     *iterator = f(value);     return *this; }  proxy &operator=(value_type &&value) {     *iterator = f(value);     return *this; } 

I would try to write simply

template<class U> proxy& operator=(U&& value) {     *iterator = f(std::forward<U>(value));     return *this; } 

I was a little worried that this might do the wrong thing when chaining transform_iterators together, but my first trivial test case worked, so I'm going to ignore that nagging worry. :) That test case was:

auto p1 = shino::transformer<std::string>(string_to_int, output.begin()); auto p2 = shino::transformer<std::string>([](std::string x){ return x+"1"; }, p1); std::copy(v.begin(), v.end(), p2); 

The other place where you use InputType is in your inheritance from std::iterator<std::output_iterator_tag, InputType, void, void, void>. Fortunately, inheritance from std::iterator is no longer considered xc3xa0 la mode; in fact, std::iterator itself may become deprecated soon! So what you should do is simply

template<typename Functor, typename Iterator> class transform_iterator {     Functor functor;     Iterator iterator;    public:     using iterator_category = std::output_iterator_tag;     using difference_type = void;     using value_type = void;     using pointer = void;     using reference = void; 

Notice that std::iterator_traits strongly encourages you to put the using value_type = void; line; and pointer, reference, and difference_type also become void by analogy to std::back_insert_iterator.

Notice that these member typedefs must be public. (libc++ is happy if they're private; but libstdc++ isn't.)


Lastly, your specialization of std::swap is also no longer xc3xa0 la mode. Instead of interjecting your own code into the std namespace, you should simply provide a shino::swap(transform_iterator&, transform_iterator&) that can be found by ADL.

Your swap code itself is 100% correct and reasonable; you should just change that namespace std to namespace shino, and you'll be in business.

The expected way for users (such as STL algorithms) to swap two transform_iterators a and b is:

using std::swap;  // in case no ADL swap exists swap(a, b);  // look for an ADL swap; fall back on std::swap if needed 

I have no particular comment on the efficiency or subtle correctness of how your code (or my revised code) handles the forwarding of functors and such. I'd probably just try to write some tests for that (e.g. what if the functor in question is move-only?), and fix any issues they find, and then move on with my life. ;)


Here's a Wandbox of the code after these changes.

 
 
   
   

Relacionados problema

2  Foreach Polymorphic STL sin pasar el tipo de contenedor  ( Polymorphic stl foreach without passing the container type ) 
Estaba tratando de averiguar cómo hacer una macro forete para hacer una macro para los contenedores de STL y me fui con este método que usa plantillas para re...

1  Iterador de secuencia numérico bidireccional (punto flotante)  ( Bidirectional numeric floating point sequence iterator ) 
Quería un iterador limpio que siempre me dará una secuencia limpia de step Números espaciados. Esto es lo que acompañé, pero soy un poco escéptico que cubrí...

7  Aplicar acción arbitraria durante la función de generador recursivo  ( Apply arbitrary action during recursive generator function ) 
Tengo esta función de generador recursivo. Traerá un objeto en busca de "hojas", cualquier tecla que no apunte a sub-objetos. Luego aplica una de las dos ac...

45  Simple ejemplo de un iterador y un iterador en Java  ( Simple example of an iterable and an iterator in java ) 
Fue un TA por un curso de programación Java de primer año este año. Como ejemplo muy simple de Iterables / Iteradores, escribí el siguiente código para los es...

4  Usando un iEnumerator sin un bucle de foreach para simular una máquina de estado  ( Using an ienumerator without a foreach loop in order to simulate a state machine ) 
Estoy trabajando con un objeto que necesita para ejecutar un método 99887766655544338 . A veces, el ExecuteEffect() solo debe ejecutarse una vez, mientras ...

3  C ++ 11 Quicksort cualquier contenedor  ( C11 quicksort any container ) 
Como dice el título, Simple Quicksort para ayudarme a acostumbrarme a las plantillas e iteradoras de C ++. La principal preocupación es si hay una mejor maner...

15  CircularListcursor: un cursor circular eficiente en cualquier lista  ( Circularlistcursor an efficient circular cursor on any list ) 
He decidido crear mi propio CircularListCursor , porque quería una abstracción y un 9988777655544338 no podría dar lo suficiente en mi opinión. También est...

4  Paginado IEnumerable <T> secuencia  ( Paginate ienumerablet sequence ) 
Usé lo siguiente para dividir la secuencia en lotes: source .Select((item, index) => new {Item = item, Index = index}) .ToLookup(x => x.Index / bat...

4  Clase de lista similar a LPP  ( Lisp like list class ) 
Entonces, aquí está mi código: .png1 Me preocupa un poco de que no tenga una clase separada para pares simples (por ejemplo: listas no terminadas correc...

7  Transformando el iterador  ( Transforming iterator ) 
La pregunta es el seguimiento de iterador de preprocesamiento . Especificación: Dado Functor functor (que es Callable3 ) y Iterator iterator4 (que ...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos