In a console program I am creating, I have a bit of code that parses through a file. After parsing each line, it is checked for syntax errors. If there is a syntax error, the program then stops reading the file and goes to the next part of the program. The problem is, it is very messy as my only solution to it so far is a series of nested if statements or a line of if statements. The problem with nested ifs is it gets very messy very fast, and a series of if statements has the program testing for several things that don't need to be tested. Heres some sudo code of my problem (note I am NOT using a return statement)
Pseudo code shown instead of real code, as it is very large
Nested if:
open file;
read line;
//Each if is testing something different
//Every error is different
if (line is valid)
{
read line;
if (line is valid)
{
read line;
if (line is valid)
{
do stuff;
}
else
error;
}
else
error;
}
else
error;
code that must be reached, even if there was an error;
Non-nested ifs:
bool fail = false;
open file;
read line;
//Each if is testing something different
//Every error is different
if (line is valid)
read line;
else
{
error;
fail = true;
}
if (!error && line is valid)
read line;
else
{
error;
fail = true;
}
if (!error && line is valid)
do stuff;
else
error;
//Note how error is constantly evaluated, even if it has already found to be false
code that must be reached, even if there was an error;
I have looked at many different sites, but their verdicts differed from my problem. This code does work at runtime, but as you can see it is not very elegant. Is there anyone who has a more readable/efficient approach on my problem? Any help is appreciated :)
Two options come to mind:
Option 1: chain reads and validations
This is similar to how std::istream extraction operators work. You could do something like this:
void your_function() {
std::ifstream file("some_file");
std::string line1, line2, line3;
if (std::getline(file, line1) &&
std::getline(file, line2) &&
std::getline(file, line3)) {
// do stuff
} else {
// error
}
// code that must be reached, even if there was an error;
}
Option 2: split into different functions
This can get a little long, but if you split things out right (and give everything a sane name), it can actually be very readable and debuggable.
bool step3(const std::string& line1,
const std::string& line2,
const std::string& line3) {
// do stuff
return true;
}
bool step2(std::ifstream& file,
const std::string& line1,
const std::string& line2) {
std::string line3;
return std::getline(file, line3) && step3(line1, line2, line3);
}
bool step1(std::ifstream& file,
const std::string& line1) {
std::string line2;
return std::getline(file, line2) && step2(file, line1, line2);
}
bool step0(std::ifstream& file) {
std::string line1;
return std::getline(file, line1) && step1(file, line1);
}
void your_function() {
std::ifstream file("some_file");
if (!step0(file)) {
// error
}
// code that must be reached, even if there was an error;
}
This example code is a little too trivial. If the line validation that occurs in each step is more complicated than std::getline's return value (which is often the case when doing real input validation), then this approach has the benefit of making that more readable. But if the input validation is as simple as checking std::getline, then the first option should be preferred.
Is there [...] a more readable/efficient approach on my problem
Step 1. Look around for a classical example of text parser
Answer: a compiler, which parses text files and produces different kind of results.
Step 2. Read some theory how does compilers work
There are lots of approaches and techniques. Books, online and open source examples. Simple and complicated.
Sure, you might just skip this step if you are not that interested.
Step 3. Apply theory on you problem
Looking through the theory, you will no miss such therms as "state machine", "automates" etc. Here is a brief explanation on Wikipedia:
https://en.wikipedia.org/wiki/Automata-based_programming
There is basically a ready to use example on the Wiki page:
#include <stdio.h>
enum states { before, inside, after };
void step(enum states *state, int c)
{
if(c == '\n') {
putchar('\n');
*state = before;
} else
switch(*state) {
case before:
if(c != ' ') {
putchar(c);
*state = inside;
}
break;
case inside:
if(c == ' ') {
*state = after;
} else {
putchar(c);
}
break;
case after:
break;
}
}
int main(void)
{
int c;
enum states state = before;
while((c = getchar()) != EOF) {
step(&state, c);
}
if(state != before)
putchar('\n');
return 0;
}
Or a C++ example with state machine:
#include <stdio.h>
class StateMachine {
enum states { before = 0, inside = 1, after = 2 } state;
struct branch {
unsigned char new_state:2;
unsigned char should_putchar:1;
};
static struct branch the_table[3][3];
public:
StateMachine() : state(before) {}
void FeedChar(int c) {
int idx2 = (c == ' ') ? 0 : (c == '\n') ? 1 : 2;
struct branch *b = & the_table[state][idx2];
state = (enum states)(b->new_state);
if(b->should_putchar) putchar(c);
}
};
struct StateMachine::branch StateMachine::the_table[3][3] = {
/* ' ' '\n' others */
/* before */ { {before,0}, {before,1}, {inside,1} },
/* inside */ { {after, 0}, {before,1}, {inside,1} },
/* after */ { {after, 0}, {before,1}, {after, 0} }
};
int main(void)
{
int c;
StateMachine machine;
while((c = getchar()) != EOF)
machine.FeedChar(c);
return 0;
}
Sure, instead of chars you should feed lines.
This technique scales up to a complicated compilers, proven with tons of implementations. So if you are looking for a "right" approach, here it is.
A common modern practice is an early return with RAII. Basically it means that the code that must happen should be in a destructor of a class, and your function will have a local object of that class. Now when you have error you exit early from the function (either with Exception or just plain return) and the destructor of that local object will handle the code that must happen.
The code will look something like this:
class Guard
{
...
Guard()
~Guard() { /*code that must happen */}
...
}
void someFunction()
{
Gaurd localGuard;
...
open file;
read line;
//Each if is testing something different
//Every error is different
if (!line)
{
return;
}
read line;
if (!line)
{
return;
}
...
}
Related
I have a problem, when I try getline, instead of scanning a line program chooses to take the whole text. I tried adding delim also I have include string for this, but it still doesn't seem to work. And the vector is just one big string.
void convert_wishlist(ifstream& file, vector<string>& wishlist, int& budget){
assert(file.is_open());
string g;
file>>budget;
while (!file.fail()) {
getline(file, g);
wishlist.push_back(g);
}
}
A while back, I had to write code ready to deal deal with text files coming from either Windows or Linux so I wrote my own version of getline that was ready for either. This has worked for me
template<class CT, class TT, class AT>
inline
std::basic_istream<CT, TT>& getline(
std::basic_istream<CT, TT>& instr,
std::basic_string<CT, TT, AT>& str)
{
using is_type = std::basic_istream<CT, TT>;
using sentry_type = typename is_type::sentry;
std::ios_base::iostate state = std::ios_base::goodbit;
auto changed = false;
const sentry_type sentry(instr, true);
if (sentry)
{
// State okay, extract characters
try
{
auto sb = instr.rdbuf();
str.erase();
const auto delimNL = TT::to_int_type('\n');
const auto delimCR = TT::to_int_type('\r');
auto ch = sb->sgetc();
for (; ; ch = sb->snextc())
{
if (TT::eq_int_type(TT::eof(), ch))
{
// End of file, quit
state |= std::ios::eofbit;
break;
}
else if (TT::eq_int_type(ch, delimNL))
{
// Got a newline, discard it and quit.
changed = true; // We did read something successfully
sb->sbumpc(); // Discard this character
break; // Quit
}
else if (TT::eq_int_type(ch, delimCR))
{
// Got a carriage return.
changed = true; // We did read something successfully
sb->sbumpc(); // Discard this character
// Next character might be a newline. If so, do the
// same for that
if (TT::eq_int_type(sb->sgetc(), delimNL))
sb->sbumpc();
break; // We are done
}
else if(str.max_size() <= str.size())
{
// String too large, quit
state |= std::ios_base::failbit;
break;
}
else
{
// Got a character, add it to string
str += TT::to_char_type(ch);
changed = true;
}
}
}
catch(...)
{
// Some exception. Set badbit and rethrow
instr.setstate(std::ios_base::badbit);
throw;
}
}
}
I want to generate clean assembly like Compiler Explorer locally. Note that, I read How to remove “noise” from GCC/clang assembly output? before attempting this. The output using that method isn't as clean or dense compared to godbolt and still has a lot of asm directives and unused labels in it.
How can I get clean assembly output without any unused labels or directives?
For the record, it is possible (and apparently not too hard) to set up a local install of Matt Godbolt's Compiler Explorer stuff, so you can use that to explore asm output for files that are part of existing large projects with their #include dependencies and everything.
If you already have some asm output, #Waqar's answer looks useful. Or maybe that functionality can be used on its own from the Compiler Explorer repo via node.js, IDK.
According to the install info in the readme in https://github.com/compiler-explorer/compiler-explorer (Matt's repo), you can simply run make after cloning it on a machine that has node.js installed.
I also found https://isocpp.org/blog/2017/10/cpp-weekly-episode-83-installing-compiler-explorerjason-turner which might have more details (or be obsolete at this point, IDK).
I think Matt also mentions using a local clone of Compiler Explorer in his CppCon 2017 talk about Compiler Explorer (maybe replying to a question at the end), “What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”, and recommends it for playing with code that uses lots of #include that would be hard to get onto https://godbolt.org/. (Or for closed-source code).
A while ago, I needed something like this locally so I wrote a small tool to make the asm readable.
It attempts to 'clean' and make the 'asm' output from 'gcc' readable using C++ itself. It does something similar to Compiler Explorer and tries to remove all the directives and unused labels, making the asm clean. Only standard library is used for this.
Some things I should mention:
Will only with gcc and clang
Only tested with C++ code
compile with -S -fno-asynchronous-unwind-tables -fno-dwarf2-cfi-asm -masm=intel, (remove -masm= if you want AT&T asm)
AT&T syntax will probably work but I didn't test it much. The other two options are to remove the .cfi directives. It can be handled using the code below but the compiler itself does a much better job of this. See the answer by Peter Cordes above.
This program can work as standalone, but I would highly recommend reading this SO answer to tune your asm output and then process it using this program to remove unused labels / directives etc.
abi::__cxa_demangle() is used for demangling
Disclaimer: This isn't a perfect solution, and hasn't been tested extensively.
The strategy used for cleaning the asm(There are probably better, faster more efficient ways to do this):
Collect all the labels
Go through the asm line by line and check if the labels are used/unused
If the labels are unused, they get deleted
Every line beginning with '.' gets deleted, unless it is a used somewhere
Update 1: Not all static data gets removed now.
#include <algorithm>
#include <cxxabi.h>
#include <fstream>
#include <iostream>
#include <regex>
#include <string>
#include <sstream>
#include <unordered_map>
// trim from both ends (in place)
std::string_view trim(std::string_view s)
{
s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));
return s;
}
static inline bool startsWith(const std::string_view s, const std::string_view searchString)
{
return (s.rfind(searchString, 0) == 0);
}
std::string demangle(std::string &&asmText)
{
int next = 0;
int last = 0;
while (next != -1) {
next = asmText.find("_Z", last);
//get token
if (next != -1) {
int tokenEnd = asmText.find_first_of(":,.#[]() \n", next + 1);
int len = tokenEnd - next;
std::string tok = asmText.substr(next, len);
int status = 0;
char* name = abi::__cxa_demangle(tok.c_str(), 0, 0, &status);
if (status != 0) {
std::cout << "Demangling of: " << tok << " failed, status: " << status << '\n';
continue;
}
std::string demangledName{name};
demangledName.insert(demangledName.begin(), ' ');
asmText.replace(next, len, demangledName);
free((void*)name);
}
}
return std::move(asmText);
}
std::string clean_asm(const std::string& asmText)
{
std::string output;
output.reserve(asmText.length());
std::stringstream s{asmText};
//1. collect all the labels
//2. go through the asm line by line and check if the labels are used/unused
//3. if the labels are unused, they get deleted
//4. every line beginning with '.' gets deleted, unless it is a used label
std::regex exp {"^\\s*[_|a-zA-Z]"};
std::regex directiveRe { "^\\s*\\..*$" };
std::regex labelRe { "^\\.*[a-zA-Z]+[0-9]+:$" };
std::regex hasOpcodeRe { "^\\s*[a-zA-Z]" };
std::regex numericLabelsRe { "\\s*[0-9]:" };
const std::vector<std::string> allowedDirectives =
{
".string", ".zero", ".byte", ".value", ".long", ".quad", ".ascii"
};
//<label, used>
std::unordered_map<std::string, bool> labels;
//1
std::string line;
while (std::getline(s, line)) {
if (std::regex_match(line, labelRe)) {
trim(line);
// remove ':'
line = line.substr(0, line.size() - 1);
labels[line] = false;
}
}
s.clear();
s.str(asmText);
line = "";
//2
while (std::getline(s, line)) {
if (std::regex_match(line, hasOpcodeRe)) {
auto it = labels.begin();
for (; it != labels.end(); ++it) {
if (line.find(it->first)) {
labels[it->first] = true;
}
}
}
}
//remove false labels from labels hash-map
for (auto it = labels.begin(); it != labels.end();) {
if (it->second == false)
it = labels.erase(it);
else
++it;
}
s.clear();
s.str(asmText);
line = "";
std::string currentLabel;
//3
while (std::getline(s, line)) {
trim(line);
if (std::regex_match(line, labelRe)) {
auto l = line;
l = l.substr(0, l.size() - 1);
currentLabel = "";
if (labels.find(l) != labels.end()) {
currentLabel = line;
output += line + "\n";
}
continue;
}
if (std::regex_match(line, directiveRe)) {
//if we are in a label
if (!currentLabel.empty()) {
auto trimmedLine = trim(line);
for (const auto& allowedDir : allowedDirectives) {
if (startsWith(trimmedLine, allowedDir)) {
output += line;
output += '\n';
}
}
}
continue;
}
if (std::regex_match(line, numericLabelsRe)) {
continue;
}
if (line == "endbr64") {
continue;
}
if (line[line.size() - 1] == ':' || line.find(':') != std::string::npos) {
currentLabel = line;
output += line + '\n';
continue;
}
line.insert(line.begin(), '\t');
output += line + '\n';
}
return output;
}
int main(int argc, char* argv[])
{
if (argc < 2) {
std::cout << "Please provide more than asm filename you want to process.\n";
}
std::ifstream file(argv[1]);
std::string output;
if (file.is_open()) {
std::cout << "File '" << argv[1] << "' is opened\n";
std::string line;
while (std::getline(file, line)) {
output += line + '\n';
}
}
output = demangle(std::move(output));
output = clean_asm(output);
std::string fileName = argv[1];
auto dotPos = fileName.rfind('.');
if (dotPos != std::string::npos)
fileName.erase(fileName.begin() + dotPos, fileName.end());
std::cout << "Asm processed. Saving as '"<< fileName <<".asm'";
std::ofstream out;
out.open(fileName + ".asm");
out << output;
return 0;
}
I checked Compiler Explorer to see if they had a specific set of compiler options to get their output. But they don't. Instead they filter the assembly listing with this function. There's also an additional processing step that merges the debug info into source and assembly highlighting.
To answer your question, I don't think it's possible with GCC itself right now (Sep 2021).
I am learning c++ so bear with me and apologize for any idiocy beforehand.
I am trying to write some code that matches the first word on each line in a file called "command.txt" to either "num_lines", "num_words", or "num_chars".
If the first word of the first line does not match the previously mentioned words, it reads the next line.
Once it hits a matching word (first words only!) it prints out the matching word.
Here is all of my code:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
ifstream comm_in("commands.txt"); // opens file
string command_name = "hi"; // stores command from file
bool is_command() {
if (command_name == "num_words" || command_name == "num_chars" || command_name == "num_lines") {
return true;
} else {
return false;
}
}
// FIND a first word of a line in file THAT MATCHES "num_words", "num_chars" or "num_lines"
void get_command() {
string line;
char c;
while (!is_command()) { // if command_name does not match a command
// GET NEXT LINE OF FILE TO STRING
getline(comm_in, line);
// SUPPOSED TO GET THE FIRST WORD OF A STRING (CANT USE SSTREAM)
for (int i = 0; i < line.size(); i++) { // increment through line
c = line[i]; // assign c as index value of line
if (c == ' ' || c == '\t') { // if c is a space/tab
break; // end for loop
} else {
command_name += c; // concatenate c to command_name
} // if
} // for
} // while
return;
}
int main() {
get_command();
cout << command_name; // supposed to print "num_lines"
}
The contents of the command.txt file:
my bear is happy
and that it
great ha
num_lines sigh
It compiles properly, but when I run it in my terminal, nothing shows up; it doesn't seem to ever stop loading.
How can I fix this?
Unless you really want to hate yourself in the morning (so to speak) you want to get out of the habit of using global variables. You'll also almost certainly find life easier if you break get_command into (at least) two functions, one specifically to get the first word from the string containing the line.
I'd write the code more like this:
bool is_cmd(std::string const &s) {
return s == "num_words" || s == "num_chars" || s == "num_lines";
}
std::string first_word(std::istream &is) {
std::string line, ret;
if (std::getline(is, line)) {
auto start = line.find_first_not_of(" \t");
auto end = line.find_first_of(" \t", start);
ret = line.substr(start, end - start);
}
return ret;
}
void get_command(std::istream &is) {
std::string cmd;
while (!(cmd = first_word(is)).empty())
if (is_cmd(cmd)) {
std::cout << cmd;
break;
}
}
This still isn't perfect (e.g., badly formed input could still cause it to fail) but at least it's a move in what I'd say is a better direction.
If something goes wrong and you reach the end of file the loop will never stop. You should change getline(comm_in, line) to if(!getline(comm_in, line)) break;, or better yet, use that as the condition for the loop.
You also have to reset command_name for each pass:
while(getline(comm_in, line))
{
command_name = "";
for(int i = 0; i < line.size(); i++)
{
c = line[i];
if(c == ' ' || c == '\t')
break;
else
command_name += c;
}
if(is_command())
break;
}
// FIND a first word of a line in file THAT MATCHES "num_words", "num_chars" or "num_lines"
void get_command()
{
string line;
char c;
while (!is_command()) { // if command_name does not match a command
// GET NEXT LINE OF FILE TO STRING
if(getline(comm_in, line),comm_in.fail()){
// end reading
break;
}
//clear
command_name = "";
// SUPPOSED TO GET THE FIRST WORD OF A STRING (CANT USE SSTREAM)
for (int i = 0; i < line.size(); i++) { // increment through line
c = line[i]; // assign c as index value of line
if (c == ' ' || c == '\t') { // if c is a space/tab
break; // end for loop
} else {
command_name += c; // concatenate c to command_name
} // if
} // for
} // while
return;
}
The key of this problem is that you didn't clear the command_name.
What's more, you have to add a judge about whether reaching the end of the file.
ps: if(getline(comm_in, line),comm_in.fail()) is equal to if(getline(comm_in, line)),
I've been going through Stroustrup's Programming and Principles to teach myself c++11.
In chapter 11, he describes a program that removes (turns into whitespace) any un-wanted characters from an input stream. So, for example, I could set a string to hold the characters '!' and '.'. And then I could input
dog! food and receive the output dog food .
However, I'm not understanding how the string, word in main
int main ()
{
Punct_stream ps {cin};
ps.whitespace(";:,.?!()\"{}<>/&$##%^*|~");
ps.case_sensitive(false);
cout<<"Please input words."<<"\n";
vector<string> vs;
for (string word; ps>>word;)// how does word get assigned a string? {
vs.push_back(word);
}
sort(vs.begin(), vs.end());
for (int i = 0; i<vs.size(); ++i) {
if (i==0 || vs[i]!=vs[i-1]) cout<<vs[i]<<"\n";
}
}
is assigned a value through the overloaded definition of >>.
Punct_stream& Punct_stream::operator>>(string& s)
{
while (!(buffer>>s)) {
if (buffer.bad() || !source.good()) return *this;
buffer.clear();
string line;
getline(source,line); // get a line from source
for (char& ch : line)
if (is_whitespace(ch))
ch = ' ';
else if (!sensitive)
ch = tolower(ch);
buffer.str(line); //how does word become this value?
}
return *this;
}
Obviously, pointer this will be the result of >>, but I don't understand how that result includes assigning word the string of istringstream buffer. I only know the basics of pointers, so maybe that's my problem?
#include<iostream>
#include<sstream>
#include<string>
#include<vector>
using namespace std;
class Punct_stream {
public:
Punct_stream(istream& is)
: source{is}, sensitive{true} { }
void whitespace(const string& s) { white = s; }
void add_white(char c) { white += c; }
bool is_whitespace(char c);
void case_sensitive(bool b) { sensitive = b; }
bool is_case_sensitive() { return sensitive; }
Punct_stream& operator>>(string& s);
operator bool();
private:
istream& source;
istringstream buffer;
string white;
bool sensitive;
};
Punct_stream& Punct_stream::operator>>(string& s)
{
while (!(buffer>>s)) {
if (buffer.bad() || !source.good()) return *this;
buffer.clear();
string line;
getline(source,line); // get a line from source
for (char& ch : line)
if (is_whitespace(ch))
ch = ' ';
else if (!sensitive)
ch = tolower(ch);
buffer.str(line); //how does word become this value?
}
return *this;
}
Punct_stream::operator bool()
{
return !(source.fail() || source.bad()) && source.good(); }
bool Punct_stream::is_whitespace(char c) {
for (char w : white)
if (c==w) return true; return false;
}
int main ()
{
Punct_stream ps {cin};
ps.whitespace(";:,.?!()\"{}<>/&$##%^*|~");
ps.case_sensitive(false);
cout<<"Please input words."<<"\n";
vector<string> vs;
for (string word; ps>>word;)// how does word get assigned a string? {
vs.push_back(word);
}
sort(vs.begin(), vs.end());
for (int i = 0; i<vs.size(); ++i) {
if (i==0 || vs[i]!=vs[i-1]) cout<<vs[i]<<"\n";
}
}
The trick is that the while loop inside operator >> has opposite logic to what you normally do when reading from a stream. Normally, you'd do something like this (and main does it, in fact):
while (stream >> aString)
Notice, however, that the while in the extractor has a negation:
Try extracting s from buffer. If you fail, do one iteration of the loop and try again.
At start, buffer is empty so extracting s will fail and the loop body will be entered. What the loop body does is read a line from source (the stream being wrapped), transform selected characters of that line into whitespace, and set this line as the content of buffer (via the buffer.str(line); call).
So, after the line was transformed, it is queued into buffer. Then the next iteration of the loop comes, and it again tries to extract s from buffer. If the line had any non-whitespace, the first word will be extracted (and the rest will remain in buffer for further readings). If the line had whitespace only, the loop body is entered again.
Once s is successfully extracted, the loop terminates and the function exits.
On next call, it will work with whatever was left in buffer, re-filling buffer from source as necessary (by the process I've explained above).
I wrote a simple reader and parser for a graph file format. The problem is that it is incredibly slow. Here are the relevant methods:
Graph METISGraphReader::read(std::string path) {
METISParser parser(path);
std::pair<int64_t, int64_t> header = parser.getHeader();
int64_t n = header.first;
int64_t m = header.second;
Graph G(n);
node u = 0;
while (parser.hasNext()) {
u += 1;
std::vector<node> adjacencies = parser.getNext();
for (node v : adjacencies) {
if (! G.hasEdge(u, v)) {
G.insertEdge(u, v);
}
}
}
return G;
}
std::vector<node> METISParser::getNext() {
std::string line;
bool comment = false;
do {
comment = false;
std::getline(this->graphFile, line);
// check for comment line starting with '%'
if (line[0] == '%') {
comment = true;
TRACE("comment line found");
} else {
return parseLine(line);
}
} while (comment);
}
static std::vector<node> parseLine(std::string line) {
std::stringstream stream(line);
std::string token;
char delim = ' ';
std::vector<node> adjacencies;
// split string and push adjacent nodes
while (std::getline(stream, token, delim)) {
node v = atoi(token.c_str());
adjacencies.push_back(v);
}
return adjacencies;
}
To diagnose why it is so slow, I ran it in a profiler (Apple Instruments). The results were surprising: It's slow because of locking overhead. The program spends over 90% of its time in pthread_mutex_lock and _pthread_cond_wait.
I have no idea where the locking overhead comes from, but I need to get rid of it. Can you suggest next steps?
EDIT: See the call stack expanded for _pthread_con_wait. I cannot figure out the source of the locking overhead by looking at this:
Expand the call stack on the _pthread_cond_wait and pthread_mutex_lock calls to find out where the locking calls are invoked from.
As a guess I'm going to say it's in all the unnecessary heap allocations you're doing. The heap is a thread safe resource and on this platform the thread safety could be provided via mutexes.
All functions that read data from an istream will lock a mutex, read data from a streambuf and unlock the mutex. To eliminate that overhead, read the file directly from the streambuf instead of the istream and don't use stringstream to parse the data.
Here is a version of getline that uses streambuf instead of istream
bool fastGetline(streambuf* sb, std::string& t)
{
t.clear();
for(;;) {
int c = sb->sbumpc();
switch (c) {
case '\n':
return true;
case '\r':
if(sb->sgetc() == '\n')
sb->sbumpc();
return true;
case EOF:
return !t.empty();
default:
t += (char)c;
}
}