canellas
canellas

Reputation: 697

How to read strings with spaces from a ifstream using istream_iterator?

I want to read a text file, line by line, using istream_iterator, but it fails when there is a white space in the line.

This is a sample code:

#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>

int main(int argc, char** argv)
{
  if (argc != 2) {
    std::cout << argv[0] << " <file-name>" << std::endl;
    return 10;
  }

  std::ifstream _in(argv[1]);
  if (!_in.good()) {
      std::cout << "error reading " << argv[1] << std::endl;
      return 20;
  }

  _in.unsetf(std::ios_base::skipws);

  std::istream_iterator<std::string> _ite(_in);
  std::istream_iterator<std::string> _end;
  std::string _s(*_ite);

  std::cout << "line read " << _s << std::endl;
}

For example, in this input file: 1;3.14;bla bla -3;0.923;let me go

The first string read is 1;3.14;bla

Is there a way to do it, or should I give up and use getline?

Upvotes: 1

Views: 1809

Answers (2)

A M
A M

Reputation: 15277

No. Do not give up. Use the C++ approach. Although I will also use std::getline, I think it is rather C-Style. And therefor I will wrap this functrion in a proxy class.

I find your idea good, to use a std::istream_iterator. This is the "more modern" C++ way of doing things. And the big advantage is that you can use the istream_iterator in algorithms.

The only problem to solve is, to implement the abstract model of a "line" into a class. And as I will show below that is fairly easy.

Using a proxy class is the standard approach and you will find a lot of examples here on SO doing exactly that.

Please see:

#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <sstream>
#include <ios>


std::istringstream testFile{R"(Line1Word1 Line1Word2 Line1Word3 
Line2Word1 Line2Word2
Line3Word1 Line3Word2 Line3Word3  Line3Word4
Line4Word1 Line4Word2 Line4Word3
)"};

struct Line      // ! This is a proxy for the input_iterator and output iterator! 
{   
    // Input function. Read on line of text file and split it in columns
    friend std::istream& operator>>(std::istream& is, Line& line) {
        return std::getline(is, line.lineTemp ); 
    }

    // Output function. 
    friend std::ostream& operator<<(std::ostream& os, const Line& line) {
        return os <<  line.lineTemp; 
    }

    // cast to needed result
    operator std::string() const { return lineTemp; }  
    // Temporary Local storage for line
    std::string lineTemp{};  
};


int main()
{
    std::cout << "\n\nTest 1. Read all lines into a vector:\n";
    std::vector<std::string> allLines {std::istream_iterator<Line>(testFile),std::istream_iterator<Line>() };
    std::copy(allLines.begin(), allLines.end(), std::ostream_iterator<std::string>(std::cout, "\n"));

    std::cout << "\n\nTest 2: Display fist 2 in file\n";
    testFile.clear(); testFile.seekg(0,std::ios::beg);
    std::copy_n(std::istream_iterator<Line>(testFile),2,std::ostream_iterator<std::string>(std::cout, "\n"));

    testFile.clear(); testFile.seekg(0,std::ios::beg);
    std::cout << "\n\nTest 3: Number of lines in File\n"
        << std::distance(std::istream_iterator<Line>(testFile),std::istream_iterator<Line>());

    std::cout << "\n\nTest 4: Use iterator separately\n";
    testFile.clear(); testFile.seekg(0,std::ios::beg);
    // Define the iterator
    std::istream_iterator<Line> lineIterator(testFile);
    // Dereference iterator
    std::cout << *lineIterator << "\n";

    return 0;
}

Upvotes: 1

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153840

When a std::string is read it reads until the first space character is found. What qualifies as a space character is defined by the stream’s std::locale, more precisely its std::ctype<char> facet. You can create a std::ctype<char> facet which considers only ’\n’ as a space, create a std::locale object containing that facet, imbue() the stream with the corresponding std::locale and it should work.

Upvotes: 3

Related Questions