Writing stream `operator>>` for serial number? - c++

I have a serial number class of the following form:
class SerialNumber { ... }
and I want to write the operator>> for it:
istream& operator>>(istream& i, SerialNumber& s)
{
???
return i;
}
The serial numbers are always 19 characters long and start with a hex digit.
I am confused if I should istream.read 19 characters. It may include prefix whitespace. ?
Or whether I should read a i >> std::string and then check that it is 19 characters long. When you read a std::string it skips whitespace (is there a standard way to implement that?) Further if I read a std::string it may have a valid 19 character serial number prefix, and I may have "over-read" the input. ?
Update:
inline istream& operator>>(istream& is, SerialNumber& id)
{
ostringstream os;
is >> ws;
for (int i = 0; i < 19; i++)
{
char c;
is >> c;
os << c;
}
id = DecodeId(os.str());
return is;
}
Partially sanitized version of Dietmar Kühl code:
istream& operator>> (istream& in, SerialNumber& sn)
{
constexpr size_t n = 19;
istream::sentry se(in);
if (!se)
return in;
istreambuf_iterator<char> it(in.rdbuf()), end;
if (it == end || !isxdigit(*it))
{
in.setstate(ios_base::failbit);
return in;
}
string s(n,'?');
for (size_t i = 0; it != end && i < n && !isspace(char(*it)), ++i)
s[i] = *it++;
sn = DecodeId(s);
if (failed to decode)
in.setstate(ios_base::failbit);
return in;
}

The standard formatted input functions always follow the same pattern:
They start off with constructing a std::sentry object which handles any skipping of leading whitespace depending on the setting of the std::ios_base::skipws formatting flag.
The read value is unchanged if reading the value fails in any way and std::ios_base::failbit gets set.
Characters are consumed up to the first character which fails to match the format.
That is, the input function would look something like that:
std::istream& operator>> (std::istream& in, SerialNumber& s) {
std::istream::sentry kerberos(in);
if (kerberos) {
std::istreambuf_iterator<char> it(in.rdbuf()), end;
char buffer[20] = {};
int i(0);
if (it != end && std::isxdigit(static_cast<unsigned char>(*it))) {
for (; it != end && i != 19
&& !std::isspace(static_cast<unsigned char>(*it)); ++i) {
buffer[i] = *it++;
}
}
if (i == 19) {
SerialNumber(buffer).swap(s);
}
else {
in.setstate(std::ios_base::failbit);
}
}
return in;
}

You should do it one step at a time:
If you want to always skip whitespace, then start by doing i >> std::ws. The stream may not have the skipws flag set. Otherwise let the user decide whether to skip whitespace or not, and set the stream error bit when reading a whitespace.
Read the first char, see if its an hexadecimal digit. If its not, then set the stream error bit.
Read the rest of the 18 characters, and as soon as you find a character that does not meet the serial number format set the stream error bit.
You should disable skipws for this, otherwise you will get valid results from characters separated by whitespace. If you do, then make sure to restore the skipws flag when exiting the function (which may happen via an exception when setting the error bit, if exceptions are enabled on the stream).

Related

c++ how to read stream till end of line

I want to read an input like this from file
sphere 3 2 3 4
pyramid 2 3 4 12 3 5 6 7 3 2 4 1 2 3
rectangle 2 3 4 1 9 12
I want to do something like this
char name[64];
int arr[12];
ifstream file (..);
while(file)
{
file >> name;
while( //reach end of line)
file >> arr[i]
}
As you can see I don't know how many integers will be entered, that's why I want to stop at new line. I did it with getline, and then splitting the line, but they told me it can be done only with >> operator.
Note: I can't use std::string or std::vector.
The simple version is to use a manipulator similar to std::ws but instead of skipping all whitespace setting std::ios_base::failbit when a newline is encountered. This manipulator would then be used to instead of skipping whitespace implicitly whitespace other than newlines are skipped. For example (the code isn't test but I think something like this with the bugs and compilation errors removed should work):
std::istream& my_ws(std::istream& in) {
std::istream::sentry kerberos(in);
while (isspace(in.peek())) {
if (in.get() == '\n') {
in.setstate(std::ios_base::failbit);
}
}
return in;
}
// ...
char name[64];
int array[12];
while (in >> std::setw(sizeof(name)) >> name) { // see (*) below
int* it = std::begin(array), end = std::end(array);
while (it != end && in >> my_ws >> *it) {
++it;
}
if (it != end && in) { deal_with_the_array_being_full(); }
else {
do_something_with_the_data(std::begin(array), it);
if (!in.eof()) { in.clear(); }
}
}
My personal guess is that the assignment asked for reading the values into char arrays followed by converting them using atoi() or strol(). I think that would be a boring solution to the exercise.
(*) Never, not even in exmaple code, use the formatted input operator with a char array array without also setting the maximum allowed size! The size can be set by setting the stream's width(), e.g., using the manipulator std::setw(sizeof(array)). If the width() is 0 when using the formatted input operator with a char array, an arbitrary number of non-whitespace characters is read. This can easily overflow the array and become a security problem! Essentially, this is the C++ way of spelling C's gets() (which is now removed from both the C and the C++ standard libraries).
I suppose that you can use peek method:
while (file)
{
file >> name;
int i = 0;
while(file.peek() != '\n' && file.peek() != EOF) {
file >> arr[i++];
}
}

Overloading >> operator for dynamic c string class

I need to overloading the cin >> operator for my c string class. I have overloaded the operator before but don't understand how to do this dynamically without having the size before hand to create the c string.
This is for homework and I must not use the string class. I also have to use dynamic allocation.
This is what I have so far... I know it's probably very poorly written, forgive me I'm a beginner.
istream& operator>> (istream& is, MyString& s1)
{
MyString temp;
int size = 0;
int i = 0;
int j = 0;
while (isspace(temp.data[i]) == true) {
is.get(temp.data[i]);
i++;
}
while (isspace(temp.data[j]) != true) {
size++;
temp.grow(size);
is >> temp.data[j];
j++;
}
return is;
}
Without seeing exactly how your MyString class is implemented, we can only speculate about how best to implement streaming into it, but typically you should implement your custom operator>> something like this:
istream& operator>> (istream& is, MyString& str)
{
istream::sentry s(is, false); // prepare the stream for input (flush output, skip leading whitespaces, error checking, etc)
if (s) // is the stream ready?
{
// clear str as needed
streamsize N = is.width();
if (N == 0) N = ... ; // set to max size of str, or numeric_limits<size_t>::max()
char ch;
while (is.get(ch)) // while not EOF or failure
{
// append ch to str, growing its capacity as needed
if (--N == 0) break; // max width reached?
if (!is.peek(ch)) break; // EOF reached?
if (isspace(ch, is.getloc()) break; // trailing whitespace detected?
}
}
is.width(0); // reset effect of std::setw()
return is;
}
The STL's built-in operator>> implementation for std::string is a little bit more complicated (use of traits and facets, direct access to the istream read buffer, etc), but this is the jist of it, based on the following information from CppReference.com:
operator<<,>>(std::basic_string)
template <class CharT, class Traits, class Allocator>
std::basic_istream<CharT, Traits>&
operator>>(std::basic_istream<CharT, Traits>& is,
std::basic_string<CharT, Traits, Allocator>& str);
Behaves as an FormattedInputFunction. After constructing and checking the sentry object, which may skip leading whitespace, first clears str with str.erase(), then reads characters from is and appends them to str as if by str.append(1, c), until one of the following conditions becomes true:
- N characters are read, where N is is.width() if is.width() > 0, otherwise N is str.max_size()
- the end-of-file condition occurs in the stream is
- std::isspace(c,is.getloc()) is true for the next character c in is (this whitespace character remains in the input stream).
If no characters are extracted then std::ios::failbit is set on is, which may throw std::ios_base::failure.
Finally, calls os.width(0) to cancel the effects of std::setw, if any.

Reading spaces from file

I have the following code which reads text from a file and stores the characters in a vector. However this code is not reading spaces and pushing them in the vector. I tried to use myRf>>noskipws but its not working.
int a;
int b;
int outp;
if (myRF.is_open())
{
while (!myRF.eof())
{
myRF >> a;
myRF >> b;
// myRf>>noskipws
for (int i=0; i<a; i++)
{
vector <char> col;
for (int j=0; j<b; j++)
{
myRF>>outp;
col.push_back(outp);
}
grid.push_back(col);
}
}
}
myRF.close();
When you enable std::noskipws leading whitespace isn't skipped. However, you try to read an int which can't start with a space! You should read a variable of type char to read, well, chars. That should just work.
Note that it is much faster to read chars using std::istreambuf_iterator<char>:
std::istream::kerberos(myRF);
if (kerberos) {
std::istreambuf_iterator<char> it(myRF, true), end;
while (it = end /* && other condition */) {
char c = *it;
++it;
// do other stuff
}
}
BTW, do not myRF.eof() to control the loop! That doesn't work because the stream cannot predict what you will try to read! The eof() member is only useful to determine why a read failed and distinguish between legit reason (have reached te end of the file) and broken input. Instead, read and check the result, e.g.
while (myRF >> a >> b) {
// ...
}
The problem is that the >> operator uses white space to determine when to end the stream extraction. If you want to grab every character from a file and store the separately then you would use something like this:
std::vector<char> letters;
std::ifstream fin ("someFile.txt");
char ch;
while (fin.get(ch))
letters.push_back(ch);

How to cleanly extract a string delimited string from an istream in c++

I am trying to extract a string from an istream with strings as delimiters, yet i haven't found any string operations with behavior close to such as find() or substr() in istreams.
Here is an example istream content:
delim_oneFUUBARdelim_two
and my goal is to get FUUBAR into a string with as little workarounds as possible.
My current solution was to copy all istream content into a string using this solution for it and then extracting using string operations. Is there a way to avoid this unnecessary copying and only read as much from the istream as needed to preserve all content after the delimited string in case there are more to be found in similar fashion?
You can easily create a type that will consume the expected separator or delimiter:
struct Text
{
std::string t_;
};
std::istream& operator>>(std::istream& is, Text& t)
{
is >> std::skipws;
for (char c: t.t_)
{
if (is.peek() != c)
{
is.setstate(std::ios::failbit);
break;
}
is.get(); // throw away known-matching char
}
return is;
}
See it in action on ideone
This suffices when the previous stream extraction naturally stops without consuming the delimiter (e.g. an int extraction followed by a delimiter that doesn't start with a digit), which will typically be the case unless the previous extraction is of a std::string. Single-character delimiters can be specified to getline, but say your delimiter is "</block>" and the stream contains "<black>metalic</black></block>42" - you'd want something to extract "<black>metallic</black>" into a string, throw away the "</block>" delimiter, and leave the "42" on the stream:
struct Until_Delim {
Until_Delim(std::string& s, std::string delim) : s_(s), delim_(delim) { }
std::string& s_;
std::string delim_;
};
std::istream& operator>>(std::istream& is, const Until_Delim& ud)
{
std::istream::sentry sentry(is);
size_t in_delim = 0;
for (char c = is.get(); is; c = is.get())
{
if (c == ud.delim_[in_delim])
{
if (++in_delim == ud.delim_.size())
break;
continue;
}
if (in_delim) // was part-way into delimiter match...
{
ud.s_.append(ud.delim_, 0, in_delim);
in_delim = 0;
}
ud.s_ += c;
}
// may need to trim trailing whitespace...
if (is.flags() & std::ios_base::skipws)
while (!ud.s_.empty() && std::isspace(ud.s_.back()))
ud.s_.pop_back();
return is;
}
This can then be used as in:
string a_string;
if (some_stream >> Until_Delim(a_string, "</block>") >> whatevers_after)
...
This notation might seem a bit hackish, but there's precedent in Standard Library's std::quoted().
You can see the code running here.
Standard streams are equipped with locales that can do classification, namely the std::ctype<> facet. We can use this facet to ignore() characters in a stream while a certain classification is not present in the next available character. Here's a working example:
#include <iostream>
#include <sstream>
using mask = std::ctype_base::mask;
template<mask m>
void scan_classification(std::istream& is)
{
auto& ctype = std::use_facet<std::ctype<char>>(is.getloc());
while (is.peek() != std::char_traits<char>::eof() && !ctype.is(m, is.peek()))
is.ignore();
}
int main()
{
std::istringstream iss("some_string_delimiter3.1415another_string");
double d;
scan_classification<std::ctype_base::digit>(iss);
if (iss >> d)
std::cout << std::to_string(d); // "3.1415"
}

Finding end of file while reading from it

void graph::fillTable()
{
ifstream fin;
char X;
int slot=0;
fin.open("data.txt");
while(fin.good()){
fin>>Gtable[slot].Name;
fin>>Gtable[slot].Out;
cout<<Gtable[slot].Name<<endl;
for(int i=0; i<=Gtable[slot].Out-1;i++)
{
**//cant get here**
fin>>X;
cout<<X<<endl;
Gtable[slot].AdjacentOnes.addFront(X);
}
slot++;
}
fin.close();
}
That's my code, basically it does exactly what I want it to but it keeps reading when the file is not good anymore. It'll input and output all the things I'm looking for, and then when the file is at an end, fin.good() apparently isn't returning false. Here is the text file.
A 2 B F
B 2 C G
C 1 H
H 2 G I
I 3 A G E
F 2 I E
and here is the output
A
B
F
B
C
G
C
H
H
G
I
I
A
G
E
F
I
E
Segmentation fault
-
Here's is Gtable's type.
struct Gvertex:public slist
{
char Name;
int VisitNum;
int Out;
slist AdjacentOnes;
//linked list from slist
};
I'm expecting it to stop after outputting 'E' which is the last char in the file. The program never gets into the for loop again after reading the last char. I can't figure out why the while isn't breaking.
Your condition in the while loop is wrong. ios::eof() isn't
predictive; it will only be set once the stream has attempted
(internally) to read beyond end of file. You have to check after each
input.
The classical way of handling your case would be to define a >>
function for GTable, along the lines of:
std::istream&
operator>>( std::istream& source, GTable& dest )
{
std::string line;
while ( std::getline( source, line ) && line.empty() ) {
}
if ( source ) {
std::istringstream tmp( line );
std::string name;
int count;
if ( !(tmp >> name >> count) ) {
source.setstate( std::ios::failbit );
} else {
std::vector< char > adjactentOnes;
char ch;
while ( tmp >> ch ) {
adjactentOnes.push_back( ch );
}
if ( !tmp.eof() || adjactentOnes.size() != count ) {
source.setstate( std::ios::failbit );
} else {
dest.Name = name;
dest.Out = count;
for ( int i = 0; i < count; ++ i ) {
dest.AdjacentOnes.addFront( adjactentOnes[ i ] );
}
}
}
}
return source;
}
(This was written rather hastily. In real code, I'd almost certainly
factor the inner loop out into a separate function.)
Note that:
We read line by line, in order to verify the format (and to allow
resynchronization in case of error).
We set failbit in the source stream in case of an input error.
We skip empty lines (since your input apparently contains them).
We do not modify the target element until we are sure that the input
is correct.
One we have this, it is easy to loop over all of the elements:
int slot = 0;
while ( slot < GTable.size() && fin >> GTable[ slot ] ) {
++ slot;
}
if ( slot != GTable.size )
// ... error ...
EDIT:
I'll point this out explicitly, because the other people responding seem
to have missed it: it is absolutely imperative to ensure that you have
the place to read into before attempting the read.
EDIT 2:
Given the number of wrong answers this question is receiving, I would
like to stress:
Any use of fin.eof() before the input is known to fail is wrong.
Any use of fin.good(), period, is wrong.
Any use of one of the values read before having tested that the input
has succeeded is wrong. (This doesn't prevent things like fin >> a >>
b, as long as neither a or b are used before the success is
tested.)
Any attempt to read into Gtable[slot] without ensuring that slot
is in bounds is wrong.
With regards to eof() and good():
The base class of istream and ostream defines three
“error” bits: failbit, badbit and eofbit. It's
important to understand when these are set: badbit is set in case of a
non-recoverable hardward error (practically never, in fact, since most
implementations can't or don't detect such errors); and failbit is set in
any other case the input fails—either no data available (end of
file), or a format error ("abc" when inputting an int, etc.).
eofbit is set anytime the streambuf returns EOF, whether this
causes the input to fail or not! Thus, if you read an int, and the
stream contains "123", without trailing white space or newline,
eofbit will be set (since the stream must read ahead to know where the
int ends); if the stream contains "123\n", eofbit will not be set.
In both cases, however, the input succeeds, and failbit will not be
set.
To read these bits, there are the following functions (as code, since I
don't know how to get a table otherwise):
eof(): returns eofbit
bad(): returns badbit
fail(): returns failbit || badbit
good(): returns !failbit && !badbit && !eofbit
operator!(): returns fail()
operator void*(): returns fail() ? NULL : this
(typically---all that's guaranteed is that !fail() returns non-null.)
Given this: the first check must always be fail() or one of the
operator (which are based on fail). Once fail() returns true, we
can use the other functions to determine why:
if ( fin.bad() ) {
// Serious problem, disk read error or such.
} else if ( fin.eof() ) {
// End of file: there was no data there to read.
} else {
// Formatting error: something like "abc" for an int
}
Practically speaking, any other use is an error (and any use of good()
is an error—don't ask me why the function is there).
Slightly slower but cleaner approach:
void graph::fillTable()
{
ifstream fin("data.txt");
char X;
int slot=0;
std::string line;
while(std::getline(fin, line))
{
if (line.empty()) // skip empty lines
continue;
std::istringstream sin(line);
if (sin >> Gtable[slot].Name >> Gtable[slot].Out && Gtable[slot].Out > 0)
{
std::cout << Gtable[slot].Name << std::endl;
for(int i = 0; i < Gtable[slot].Out; ++i)
{
if (sin >> X)
{
std::cout << X << std::endl;
Gtable[slot].AdjacentOnes.addFront(X);
}
}
slot++;
}
}
}
If you still have issues, it's not with file reading...
The file won't fail until you actually read from past the end of file. This won't occur until the fin>>Gtable[slot].Name; line. Since your check is before this, good can still return true.
One solution would be to add additional checks for failure and break out of the loop if so.
fin>>Gtable[slot].Name;
fin>>Gtable[slot].Out;
if(!fin) break;
This still does not handle formatting errors in the input file very nicely; for that you should be reading line by line as mentioned in some of the other answers.
Try moving first two reads in the while condition:
// assuming Gtable has at least size of 1
while( fin>>Gtable[slot].Name && fin>>Gtable[slot].Out ) {
cout<<Gtable[slot].Name<<endl;
for(int i=0; i<=Gtable[slot].Out-1;i++) {
fin>>X;
cout<<X<<endl;
Gtable[slot].AdjacentOnes.addFront(X);
}
slot++;
//EDIT:
if (slot == table_size) break;
}
Edit: As per James Kanze's comment, you're taking an adress past the end of Gtable array, which is what causes segfault. You could pass the size of Gtable as argument to your fillTable() function (f.ex. void fillTable(int table_size)) and check slot is in bounds before each read.
*Edited in response to James' comment - the code now uses a good() check instead of a
!eof() check, which will allow it to catch most errors. I also threw in an is_open()
check to ensure the stream is associated with the file.*
Generally, you should try to structure your file reading in a loop as follows:
ifstream fin("file.txt");
char a = '\0';
int b = 0;
char c = '\0';
if (!fin.is_open())
return 1; // Failed to open file.
// Do an initial read. You have to attempt at least one read before you can
// reliably check for EOF.
fin >> a;
// Read until EOF
while (fin.good())
{
// Read the integer
fin >> b;
// Read the remaining characters (I'm just storing them in c in this example)
for (int i = 0; i < b; i++)
fin >> c;
// Begin to read the next line. Note that this will be the point at which
// fin will reach EOF. Since it is the last statement in the loop, the
// file stream check is done straight after and the loop is exited.
// Also note that if the file is empty, the loop will never be entered.
fin >> a;
}
fin.close();
This solution is desirable (in my opinion) because it does not rely on adding random
breaks inside the loop, and the loop condition is a simple good() check. This makes the
code easier to understand.