11

When using stdio.h, I can easily read certain kinds of formatted input like this:

FILE* fin = fopen(...);
fscanf(fin, "x = %d, y = %d", &x, &y);

The great thing about this is that I don't really have to worry about how many spaces there are between the character 'x' and the following '=', and other minor details.

In C++ it appears to me as though,

ifstream fin(...);
string s;
fin >> s;

may result in s being "x" or "x=", or even "x=12" depending on the spacing of the input.

Is there a convenient way to get behavior similar to scanf/fscanf using iostream/fstream?

4
  • Interesting! I would like to know as well :) Commented Jun 21, 2013 at 21:01
  • You can use a locale with a facet that ignores whitespace as delimiter. Commented Jun 21, 2013 at 21:30
  • 1
    @0x499602D2: the normal locale treats whitespace as a delimiter. A locale that didn't treat whitespace as a delimiter wouldn't appear helpful here (at least to me). What's (apparently) desired is that letters, commas, and equal signs be treated as whitespace (in addition to the normal whitespace characters). Commented Jun 21, 2013 at 21:33
  • @JerryCoffin I'm sure you can make one. Why don't you create it as an answer? :) Commented Jun 21, 2013 at 21:50

3 Answers 3

9

This is actually surprisingly easy, given a prerequisite. I have these three functions that I stick in a header somewhere. These allow you to stream in character literals, and string literals. I've never quite understood why these aren't standard.

#include <iostream>

//These are handy bits that go in a header somewhere
template<class e, class t, int N>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, const e(&sliteral)[N]) {
        e buffer[N-1] = {}; //get buffer
        in >> buffer[0]; //skips whitespace
        if (N>2)
                in.read(buffer+1, N-2); //read the rest
        if (strncmp(buffer, sliteral, N-1)) //if it failed
                in.setstate(std::ios::failbit); //set the state
        return in;
}
template<class e, class t>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, const e& cliteral) {
        e buffer(0);  //get buffer
        in >> buffer; //read data
        if (buffer != cliteral) //if it failed
                in.setstate(std::ios::failbit); //set the state
        return in;
}
//redirect mutable char arrays to their normal function
template<class e, class t, int N>
std::basic_istream<e,t>& operator>>(std::basic_istream<e,t>& in, e(&carray)[N]) {
        return std::operator>>(in, carray);
}

Given those, the rest is easy:

in>>'x'>>'='>>data.first>>','>>'y'>>'='>>data.second;

Proof here

For more complex situations, you probably want to use std::regex or boost::regex, or maybe a real lexer/parser.

Sign up to request clarification or add additional context in comments.

7 Comments

I see that the function that takes a string literal doesn't deal with embedded spaces like fscanf(), but still this is good. However, what is the purpose of the last function? If it is to call the 1st function, I don't see how it would work properly. It looks like it'll recursively call itself till the stack blows.
@Adrian: it looks like it, but it doesn't. The first captures const e(&)[N]. The second captures const e&. std::basic_istream::operator>> captures e&. Unfortunately, the first one also would capture e(&)[N] (nonconst), so I have an override for that that explicitly forwards it to std::operator>>(...,e(&)[N]). (Note that std::operator>> is in the std namespace, these are in the global namespace, thus no recursion)
@Adrian: Yeah, the string literal is for things like in>>"name">>'='>>value;. I suppose it might make sense to make it allow/disallow spaces like scanf though. My friends and I debated for a while about if the input of string literals should ignore preceeding whitespace and what it should do with internal whitespace. acting like fscanf makes sense, I may alter it to do that. That'd increase the complexity a lot but it'd be handy.
Using regex can be a bit of bloat depending on your needs.
@MooingDuck, hi, can you please point me in the right direction(s) in order to understand what you did here? I have a basic understanding of C++ and OOP, but very little about streams and templates and I'd like to learn more. What confuses me the most is const e(&sliteral)[N] and how that works, from my understanding that instantiates an array of objects constructed with the address of a string literal as a parameter, but at the same time I don't see how that's possible in a signature to an overloaded operator. Bottom line: I'm confused.
|
1

The short answer is "no".

A slightly longer answer is "You can probably build something that does that". For example, you could read the line of text, and then use a suitable "replace spaces with empty string" type function. Or perhaps something like this:

int x, y;
string s;
getline(cin, s, '=');
cin.get();  // Get rid of =
cin >> x;
getline(cin, s, '=');
cin >> y;

Alternatively, using cin.ignore to skip things (since the string reading is not really useful uness you want to know that 'x' and 'y' are actually 'x' and 'y'=:

int x, y;
cin.ignore(1000000, '=');  // Skip up to a '=' 
cin >> x;
cin.ignore(1000000, '=');  // Skip up to a '='
cin >> y;

This will "break" if someone enteres over 100k characters without an = sign, and there is need for error checking to see that "garbage" isn't coming in - just like fscanf does. if (cin >> x) would take care of the "detect that something went wrong, but you need to then do something sensible with the fact that it's gone wrong, which I'm not sure of right now...

Of course, since C++ supports (nearly) all of C, you can of course always use whatever members of the <cstdio> functions that you would like to use, as well. [And in at least some cases, they are actually a bit better].

3 Comments

You are not compensating for the ,, which would be required as istreams are whitespace delimited.
@Adrian His code ignores the comma completely, directly reading in the two numbers. The comma could be there or missing and this would work identically either way.
@MooingDuck True, but this could be parsed as an error, depending on requirements.
0

like you specify format in fscanf/scanf using %xx you can specify format using stream manipulators as detailed in this tutorial -

http://www.tenouk.com/Module18.html

very comprehensive. stream manipulator is near the bottom of the page.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.