c++ istream-iterator

c++ - std:: istream_iterator<> con copy_n() y amigos



istream-iterator (3)

Desafortunadamente, el implementador de copy_n no ha tenido en cuenta la lectura anticipada en el bucle de copia. La implementación de Visual C ++ funciona como espera en stringstream y std :: cin. También verifiqué el caso del ejemplo original donde el istream_iterator se construye en línea.

Aquí está la pieza clave de código de la implementación de STL.

template<class _InIt, class _Diff, class _OutIt> inline _OutIt _Copy_n(_InIt _First, _Diff _Count, _OutIt _Dest, input_iterator_tag) { // copy [_First, _First + _Count) to [_Dest, ...), arbitrary input *_Dest = *_First; // 0 < _Count has been guaranteed while (0 < --_Count) *++_Dest = *++_First; return (++_Dest); }

Aquí está el código de prueba

#include <iostream> #include <istream> #include <sstream> #include <vector> #include <iterator> int _tmain(int argc, _TCHAR* argv[]) { std::stringstream ss; ss << 1 << '' '' << 2 << '' '' << 3 << '' '' << 4 << std::endl; ss.seekg(0); std::vector<int> numbers(2); std::istream_iterator<int> ii(ss); std::cout << *ii << std::endl; // shows that read ahead happened. std::copy_n(ii, 2, numbers.begin()); int i = 0; ss >> i; std::cout << numbers[0] << '' '' << numbers[1] << '' '' << i << std::endl; std::istream_iterator<int> ii2(std::cin); std::cout << *ii2 << std::endl; // shows that read ahead happened. std::copy_n(ii2, 2, numbers.begin()); std::cin >> i; std::cout << numbers[0] << '' '' << numbers[1] << '' '' << i << std::endl; return 0; } /* Output 1 1 2 3 4 5 6 4 4 5 6 */

El siguiente fragmento lee tres enteros de std::cin ; escribe dos en numbers y descarta el tercero:

std::vector<int> numbers(2); copy_n(std::istream_iterator<int>(std::cin), 2, numbers.begin());

Espero que el código lea exactamente dos enteros de std::cin , pero resulta que este es un comportamiento correcto que cumple con los estándares. ¿Es esto un descuido en la norma? ¿Cuál es la razón de este comportamiento?

A partir de 24.5.1 / 1 en el estándar C ++ 03:

Una vez construido, y cada vez que se usa ++, el iterador lee y almacena un valor de T

Por lo tanto, en el código anterior en el punto de llamada, el iterador de flujo ya lee un entero. Desde ese punto en adelante, cada lectura realizada por el iterador en el algoritmo es una lectura anticipada, que produce el valor almacenado en caché de la lectura anterior.

El último borrador de la próxima norma, n3225 , no parece soportar ningún cambio aquí (24.6.1 / 1).

En una nota relacionada, 24.5.1.1/2 del estándar actual en referencia al istream_iterator(istream_type& s) lee

Efectos: Inicializa in_stream con s . value puede inicializarse durante la construcción o la primera vez que se hace referencia.

Con énfasis en "el value puede inicializarse ..." en lugar de " se inicializará". Esto suena en contradicción con 24.5.1 / 1, pero tal vez eso merece una pregunta propia.


Hoy me encontré con un problema muy similar, y aquí está el ejemplo:

#include <iostream> #include <sstream> #include <algorithm> #include <iterator> #include <string> struct A { float a[3]; unsigned short int b[6]; }; void ParseLine( const std::string & line, A & a ) { std::stringstream ss( line ); std::copy_n( std::istream_iterator<float>( ss ), 3, a.a ); std::copy_n( std::istream_iterator<unsigned short int>( ss ), 6, a.b ); } void PrintValues( const A & a ) { for ( int i =0;i<3;++i) { std::cout<<a.a[i]<<std::endl; } for ( int i =0;i<6;++i) { std::cout<<a.b[i]<<std::endl; } } int main() { A a; const std::string line( "1.1 2.2 3.3 8 7 6 3 2 1" ); ParseLine( line, a ); PrintValues( a ); }

La compilación del ejemplo anterior con g ++ 4.6.3 produce uno:

1.1 2.2 3.3 7 6 3 2 1 1

, y compilar con g ++ 4.7.2 produce otro resultado:

1.1 2.2 3.3 8 7 6 3 2 1

El estándar c ++ 11 dice esto sobre copy_n :

template<class InputIterator, class Size, class OutputIterator> OutputIterator copy_n(InputIterator first, Size n, OutputIterator result);

Efectos: Para cada entero no negativo i <n, realiza * (resultado + i) = * (primer + i).
Devoluciones: resultado + n.
Complejidad: Exactamente n tareas.

Como puede ver, no se especifica qué sucede exactamente con los iteradores, lo que significa que depende de la implementación.

Mi opinión es que su ejemplo no debe leer el tercer valor, lo que significa que este es un pequeño defecto en el estándar de que no han especificado el comportamiento.


No conozco la justificación exacta, pero como el iterador también tiene que admitir operador * (), tendrá que almacenar en caché los valores que lee. Permitir que el iterador almacene en caché el primer valor en la construcción simplifica esto. También ayuda a detectar el final del flujo cuando el flujo está inicialmente vacío.

Tal vez su caso de uso es uno que el comité no consideró?