Home > Enterprise >  How to translate scanf exact matching into modern c stringstream reading
How to translate scanf exact matching into modern c stringstream reading

Time:03-03

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.

  • Related