I am currenlty working on a project and I'd like to use modern cpp instead of relying on old c for reading files. For context I'm trying to read wavefront obj files.
I have this old code snippet :
const char *line;
float x, y, z;
if(sscanf(line, "vn %f %f %f", &x, &y, &z) != 3)
break; // quitting loop because couldn't scan line correctly
which I've translated into :
string line;
string word;
stringstream ss(line);
float x, y, z;
if (!(ss >> word >> x >> y >> z)) // "vn x y z"
break; // quitting loop because couldn't scan line correctly
The difference is that I use a string
to skip the first word but I'd like it to match "vn"
the same as sscanf
does.
Is this possible with stringstream
or should I keep relying on sscanf
for exact pattern matching ?
Also I'm trying to translate
sscanf(line, " %d/%d/%d", &i1, &i2, &i3);
but I'm having a hard time which again orients me towards not using modern cpp for my file reader.
CodePudding user response:
I've run into this requirement as well, and wrote a little extractor for streams that lets you match literals. The code looks like this:
#include <iostream>
#include <cctype>
std::istream& operator>>(std::istream& is, char const* s) {
if (s == nullptr)
return;
if (is.flags() & std::ios::skipws) {
while (std::isspace(is.peek()))
is.ignore(1);
while (std::isspace((unsigned char)* s))
s;
}
while (*s && is.peek() == *s) {
is.ignore(1);
s;
}
if (*s)
is.setstate(std::ios::failbit);
return is;
}
In your case, you'd use it something like this:
if (!(ss >> "vn" >> x >> y >> z))
break;
As you can see from the code, it pays attention to the skipws
state, so it'll skip leading white space if and only if you have skipws set. So if you need to match a pattern that includes a precise number of leading spaces, you want to turn skipws
off, and include those spaces in your pattern.
CodePudding user response:
I'd argue that stringstream
is not "modern C "¹. (I'll also admit we don't have something as a good replacement for scanf
; we do have std::format
that is very nice, and replaces std::cout <<
shenanigans with a syntax reminescent of Python's format strings, and is much faster. Sadly, it hasn't hit standard libraries yet.)
I'll actually say that I think your sscanf
code is cleaner, and saner than the stringstream code: sscanf leaves things in an easier-to-understand state when parsing the line fails.
There's libraries you can use to build a line parser; Boost::spirit::qi
is probably the most well-known. You can do things like
auto success = (ss >> qi::phrase_match("vn " >> qi::double >> ' ' >> qi::double >> ' ' >> qi::double, x, y, z));
If this option does not fill you with joy, love for the world and faith in all-reaching beauty, you're exactly like me and find it a poor replacement for the compactness and ease of understanding the original scanf
offers.
There's the std::regex
library these days (since C 11), and that might very well be what you're looking for! However, you'd need to write expressions for "here comes a floating point number" yourself, and that is not cool at all, either.
¹ without wanting to hurt anyone, I think the iostreams library approach was the one single standard library feature that made C awkwarder to use than most contemporary languages for anything IO-related. And that has a lasting effect on code quality.