iec entre diferencias book c++ c++11 for-loop c++17

book - diferencias entre c++ y c++ 11



¿Cómo ayuda el nuevo rango para loop en C++ 17 a Ranges TS? (2)

El rango de C ++ 11/14 fue excesivamente restringido ...

El documento WG21 para esto es open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0184r0.html que tiene la siguiente motivación:

El bucle for basado en rango existente está sobrecargado. El iterador final nunca se incrementa, disminuye o desreferencia. Exigir que sea un iterador no tiene ningún propósito práctico.

Como puede ver en el Standardese que publicó, el iterador end de un rango solo se usa en la condición de bucle __begin != __end; . Por lo tanto, el end solo debe ser una igualdad comparable al begin , y no necesita ser desreferenciable o incrementable.

... que distorsiona el operator== para iteradores delimitados.

Entonces, ¿qué desventaja tiene esto? Bueno, si tiene un rango delimitado por centinela (cadena en C, línea de texto, etc.), entonces debe calzar la condición de bucle en el operator== del iterador operator== , esencialmente así

#include <iostream> template <char Delim = 0> struct StringIterator { char const* ptr = nullptr; friend auto operator==(StringIterator lhs, StringIterator rhs) { return lhs.ptr ? (rhs.ptr || (*lhs.ptr == Delim)) : (!rhs.ptr || (*rhs.ptr == Delim)); } friend auto operator!=(StringIterator lhs, StringIterator rhs) { return !(lhs == rhs); } auto& operator*() { return *ptr; } auto& operator++() { ++ptr; return *this; } }; template <char Delim = 0> class StringRange { StringIterator<Delim> it; public: StringRange(char const* ptr) : it{ptr} {} auto begin() { return it; } auto end() { return StringIterator<Delim>{}; } }; int main() { // "Hello World", no exclamation mark for (auto const& c : StringRange<''!''>{"Hello World!"}) std::cout << c; }

Ejemplo en vivo con g ++ -std = c ++ 14, ( assembly usando gcc.godbolt.org)

El operator== anterior operator== para StringIterator<> es simétrico en sus argumentos y no depende de si el rango para es begin != end o end != begin (de lo contrario, podría hacer trampa y cortar el código a la mitad).

Para patrones de iteración simples, el compilador puede optimizar la lógica enrevesada dentro del operator== . De hecho, para el ejemplo anterior, el operator== se reduce a una sola comparación. ¿Pero esto seguirá funcionando para tuberías largas de rangos y filtros? Quién sabe. Es probable que requiera niveles de optimización heroicos.

C ++ 17 relajará las restricciones que simplificarán los rangos delimitados ...

Entonces, ¿dónde se manifiesta exactamente la simplificación? En operator== , que ahora tiene sobrecargas adicionales que toman un par iterador / centinela (en ambas órdenes, por simetría). Entonces, la lógica de tiempo de ejecución se convierte en lógica de tiempo de compilación.

#include <iostream> template <char Delim = 0> struct StringSentinel {}; struct StringIterator { char const* ptr = nullptr; template <char Delim> friend auto operator==(StringIterator lhs, StringSentinel<Delim> rhs) { return *lhs.ptr == Delim; } template <char Delim> friend auto operator==(StringSentinel<Delim> lhs, StringIterator rhs) { return rhs == lhs; } template <char Delim> friend auto operator!=(StringIterator lhs, StringSentinel<Delim> rhs) { return !(lhs == rhs); } template <char Delim> friend auto operator!=(StringSentinel<Delim> lhs, StringIterator rhs) { return !(lhs == rhs); } auto& operator*() { return *ptr; } auto& operator++() { ++ptr; return *this; } }; template <char Delim = 0> class StringRange { StringIterator it; public: StringRange(char const* ptr) : it{ptr} {} auto begin() { return it; } auto end() { return StringSentinel<Delim>{}; } }; int main() { // "Hello World", no exclamation mark for (auto const& c : StringRange<''!''>{"Hello World!"}) std::cout << c; }

Ejemplo en vivo usando g ++ -std = c ++ 1z ( assembly usando gcc.godbolt.org, que es casi idéntico al ejemplo anterior).

... y de hecho admitirá rangos de "estilo D" primitivos totalmente generales.

El documento N4382 tiene la siguiente sugerencia:

C.6 Range Facade y utilidades de adaptador [future.facade]

1 Hasta que sea trivial para los usuarios crear sus propios tipos de iteradores, todo el potencial de los iteradores no se realizará. La abstracción del rango lo hace alcanzable. Con los componentes correctos de la biblioteca, los usuarios deberían poder definir un rango con una interfaz mínima (p. Ej., Miembros current , done y next ) y tener tipos de iterador generados automáticamente. Tal plantilla de clase de fachada de rango se deja como trabajo futuro.

Esencialmente, esto es igual a los rangos de estilo D (donde estas primitivas se denominan empty , front y popFront ). Un rango de cadena delimitado con solo estas primitivas se vería así:

template <char Delim = 0> class PrimitiveStringRange { char const* ptr; public: PrimitiveStringRange(char const* c) : ptr{c} {} auto& current() { return *ptr; } auto done() const { return *ptr == Delim; } auto next() { ++ptr; } };

Si uno no conoce la representación subyacente de un rango primitivo, ¿cómo extraer iteradores de él? ¿Cómo adaptar esto a un rango que se puede usar con range- for ? Aquí hay una forma (vea también la serie de publicaciones de blog de @EricNiebler) y los comentarios de @TC:

#include <iostream> // adapt any primitive range with current/done/next to Iterator/Sentinel pair with begin/end template <class Derived> struct RangeAdaptor : private Derived { using Derived::Derived; struct Sentinel {}; struct Iterator { Derived* rng; friend auto operator==(Iterator it, Sentinel) { return it.rng->done(); } friend auto operator==(Sentinel, Iterator it) { return it.rng->done(); } friend auto operator!=(Iterator lhs, Sentinel rhs) { return !(lhs == rhs); } friend auto operator!=(Sentinel lhs, Iterator rhs) { return !(lhs == rhs); } auto& operator*() { return rng->current(); } auto& operator++() { rng->next(); return *this; } }; auto begin() { return Iterator{this}; } auto end() { return Sentinel{}; } }; int main() { // "Hello World", no exclamation mark for (auto const& c : RangeAdaptor<PrimitiveStringRange<''!''>>{"Hello World!"}) std::cout << c; }

Ejemplo en vivo usando g ++ -std = c ++ 1z ( assembly usando gcc.godbolt.org)

Conclusión : los centinelas no son solo un mecanismo lindo para presionar delimitadores en el sistema de tipos, son lo suficientemente generales como para admitir rangos primitivos de "estilo D" (que pueden no tener noción de iteradores) como una abstracción de cero sobrecarga para el nuevo C ++ 1z rango para.

El comité cambió el ciclo for basado en rango de:

  • C ++ 11:

    { auto && __range = range_expression ; for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }

  • a C ++ 17:

    { auto && __range = range_expression ; auto __begin = begin_expr ; auto __end = end_expr ; for ( ; __begin != __end; ++__begin) { range_declaration = *__begin; loop_statement } }

Y la gente dijo que esto facilitará la implementación de Ranges TS. ¿Me puede dar algunos ejemplos?


La nueva especificación permite que __begin y __end sean de diferente tipo, siempre que __end se pueda comparar con __begin por desigualdad. __end ni siquiera necesita ser un iterador y puede ser un predicado. Aquí hay un ejemplo tonto con una estructura que define miembros iniciales y end , siendo este último un predicado en lugar de un iterador:

#include <iostream> #include <string> // a struct to get the first word of a string struct FirstWord { std::string data; // declare a predicate to make '' '' a string ender struct EndOfString { bool operator()(std::string::iterator it) { return (*it) != ''/0'' && (*it) != '' ''; } }; std::string::iterator begin() { return data.begin(); } EndOfString end() { return EndOfString(); } }; // declare the comparison operator bool operator!=(std::string::iterator it, FirstWord::EndOfString p) { return p(it); } // test int main() { for (auto c : {"Hello World !!!"}) std::cout << c; std::cout << std::endl; // print "Hello World !!!" for (auto c : FirstWord{"Hello World !!!"}) // works with gcc with C++17 enabled std::cout << c; std::cout << std::endl; // print "Hello" }