I have to write a stream extraction operator>>
function which checks if the istream variable (stream) in the parameter is valid before returning it along with a delimiter (which is already provided).
I’m supposed to check that not only is stream valid (and there are no errors with it, such as it not being at the of the file, having permission, or having the right types), but I’m to do all that within a local object and later move it into place at the end if all goes well.
std::istream& operator>>(std::istream& stream, GroceryItem& groceryItem) {
GroceryItem local; // local object
char delimiter = `x{0000}`; // already provided
std::string check;
std::istringstream iss;
// implementing the rest of the code...
if (stream && stream.good()) {
// the if statement here checks if stream can be open and if there are
// no errors with it
while (std::getline(stream, check) {
if (!getline(iss, check, delimiter) {
stream.setstate(std::ios::fail) abort();
} else {
iss >> std::quoted(check, delimiter);
local._upcCode = check;
local._brandName = check;
local._productName = check;
local._price = check;
}
stream >> std::quoted(local._upcCode) >> delimiter
>> std::quoted(local._brandName) >> delimiter
>> std::quoted(local._productName) >> delimiter
>> local._price >> delimiter;
}
groceryItem = std::move(local)
} else {
return stream.setstate(std::ios::fail);
}
return stream;
}
Here is a sample of the stream
"00072250018548","Nature's Own","Nature's Own Butter Buns Hotdog - 8 Ct",10.79
"00028000517205", "Nestle" ,
"Nestle Media Crema Table Cream" ,
17.97
"00034000020706" ,
"York",
"York Peppermint Patties Dark Chocolate Covered Snack Size",
12.64 "00038000570742",
"Kellogg's", "Kellogg's Cereal Krave Chocolate",
18.66
"00014100072331" , "Pepperidge Farm", "Pepperidge Farm Classic Cookie Favorites", 14.43
"00000000000000", "incomplete / invalid item"
So my question is, would all this compile? Or perhaps is there a simple way of doing it?
4
Notes regarding the OP
So my question is, would all this compile? Or perhaps is there a simple way of doing it?
There are two questions here. The answers are: (1) Try it, and see. (2) Yes! See class Student
below.
A problem with delimiter
When I plugged the code from the OP into a test framework I had created, initially, I got just a few errors. The first was a complaint about the C++23 character literal used to initialize variable delimiter
. It is not yet supported by my compiler.
char delimiter = 'x{ 0000 }'; // A new kind of character literal from C++23
Since I was planning on changing this to a simple comma, anyway, I went ahead and did that.
char const delimiter = ',';
Unmatched parentheses
The next complaint was about a couple of unmatched parentheses, so I fixed that.
while (std::getline(stream, check) { // <------ missing )
if (!getline(iss, check, delimiter) { // <------ missing )
stream.setstate(std::ios::fail) abort();
}
//...
}
A bevy of errors
With those preliminaries out of the way, I was finally able to get the compiler to tell me what it really thinks, and the results were not good.
It gave me a bevy of errors, more than 130 lines worth, beginning with these.
1>C:UsersblotcOneDriveDocumentsProgrammingStackOverflowAnswersStackOverflow_78980542_ExtractGroceryItem_Question.cpp(57,47): error C3867: 'std::ios_base::fail': non-standard syntax; use '&' to create a pointer to member
1>C:UsersblotcOneDriveDocumentsProgrammingStackOverflowAnswersStackOverflow_78980542_ExtractGroceryItem_Question.cpp(57,53): error C2146: syntax error: missing ';' before identifier 'abort'
1>C:UsersblotcOneDriveDocumentsProgrammingStackOverflowAnswersStackOverflow_78980542_ExtractGroceryItem_Question.cpp(64,36): error C2440: '=': cannot convert from 'std::string' to 'double'
1> C:UsersblotcOneDriveDocumentsProgrammingStackOverflowAnswersStackOverflow_78980542_ExtractGroceryItem_Question.cpp(64,36):
There was enough wrong that I decided it would better to demonstrate how to do it right, rather than to address each error, one-by-one.
A similar operator>>
, for class Student
This appears to be some sort of homework assignment, so let’s go with a similar student/GPA example, instead grocery items.
In this example, and also in the OP, the sample data are structured into records.
- Each record has 4 fields.
- Fields are comma-separated, but there is no comma after the 4th field.
- Whitespace can appear both before and after a delimiting comma.
- The first 3 fields are quoted strings.
- The 4th field is a floating-point number.
- Any amount of whitespace can appear between records.
You can see the data I used for testing in function sample_data
below. It includes one incomplete record at the end.
Function operator>>
is a hidden friend
operator>>
is defined within class Student
, as a hidden friend. That way, it has access to the private data members of the class.
friend std::istream& operator>>(std::istream& stream, Student& student)
{
Student local;
char const delimiter{ ',' }; // Use comma, NOT 'x{0000}'
char a, b, c;
stream >> std::skipws
>> std::quoted(local._studentID)
>> a >> std::quoted(local._studentName)
>> b >> std::quoted(local._major)
>> c >> local._gpa;
if (stream.fail() ||
a != delimiter ||
b != delimiter ||
c != delimiter) {
stream.setstate(std::ios_base::failbit);
}
else {
student = std::move(local);
}
return stream;
}
Fail when a field cannot be extracted
The stream extraction is very similar to one of the operations from the OP. It chains together several extractions. If any one of them fails, stream
will be placed into a failed state, and subsequent input operations will fail.
stream >> std::skipws
>> std::quoted(local._studentID)
>> a >> std::quoted(local._studentName)
>> b >> std::quoted(local._major)
>> c >> local._gpa;
The 4th field must be a well-formed, floating-point number. This helps to trap errors.
You can experiment below, by changing the sample data, to invalidate one or more of the fields.
-
If a number cannot be extracted from the 4th field, input fails.
-
If a string field contains embedded whitespace, and the quote mark is missing at its beginning, then the field will be interpreted to be two (or more) fields. This causes the data to become unsynchronized. Subsequent string extractions may appear to succeed, but when a string is encountered at what is supposed to be the 4th field, extraction will fail.
-
When the quote mark is missing at the end of a string, the following delimiter will be merged with the field. As before, this will cause the data to become unsynchronized, and extraction will fail when the 4th field turns out not to be a number.
Due to the way std::quoted
works, if you remove both of the quote marks from a string field, extraction may succeed. This happens if the following conditions are true.
- The field does not contain embedded whitespace, i.e., it is a “one-word” field.
- The following delimiter is preceeded by whitespace, so that it will not be merged into the string.
Fail when the delimiter
is invalid
Note that the delimiter is ','
, rather than 'x{0000}'
.
As delimiter characters are read in, each is stored in a separate character variable, a
, b
, or c
. That makes it possible to verify that each one is a comma.
If any of them is not a comma, the following if-statement will place the stream into a failed state.
One thing worth pointing out: If the stream has already failed, it is not necessary to set its failbit
a second time. We do it here, just to avoid a tangle of if-statements.
if (stream.fail() ||
a != delimiter ||
b != delimiter ||
c != delimiter) {
stream.setstate(std::ios_base::failbit);
}
Save and restore stream state
In operator>>
(above), and also in operator<<
, from the testing framework below, I/O manipulators are used to change the state of a stream.
- Prior to input,
std::skipws
is “extracted” from the stream. - Prior to ouput,
std::fixed
andstd::setprecision
are “inserted” into the stream.
In production code, you might need to save the current settings of a stream, before they are modified by I/O manipulators such as the ones above. Then, after I/O is complete, you can restore the original stream settings.
For a homework assignment, such as this, that has been omitted.
Testing framework
Source code for the testing framework I used is given below.
Here are a few notes.
-
In the OP,
operator>>
contains a loop that inputs multiple records. That loop has been moved into functionmain
below. -
In the OP,
operator>>
contains what appears to be a check to see whether an input file was successfully opened. That check belongs in functionmain
, as does the file-open operation itself. -
The program below avoids using a file, by placing the sample data into a
std::istringstream
object, and reading from that.
// main.cpp
#include <iomanip> // quoted, setprecision
#include <iostream> // cout, fixed, ios_base, istream, ostream
#include <sstream> // istringstream
#include <string> // getline, string
#include <utility> // move
//======================================================================
// Student
//======================================================================
class Student
{
std::string _studentID;
std::string _studentName;
std::string _major;
double _gpa{};
public:
Student()
= default;
Student(
std::string studentID,
std::string studentName,
std::string major,
double gpa)
: _studentID{ studentID }
, _studentName{ studentName }
, _major{ major }
, _gpa{ gpa }
{}
// More member functions
// ...
friend std::ostream& operator<<(std::ostream& stream, Student const& student)
{
stream << std::fixed << std::setprecision(1)
<< "Student ID : " << student._studentID
<< "nStudent name : " << student._studentName
<< "nMajor : " << student._major
<< "nGPA : " << student._gpa
<< 'n';
return stream;
}
friend std::istream& operator>>(std::istream& stream, Student& student)
{
Student local;
char const delimiter{ ',' }; // Use comma, NOT 'x{0000}'
char a, b, c;
stream >> std::skipws
>> std::quoted(local._studentID)
>> a >> std::quoted(local._studentName)
>> b >> std::quoted(local._major)
>> c >> local._gpa;
if (stream.fail() ||
a != delimiter ||
b != delimiter ||
c != delimiter) {
stream.setstate(std::ios_base::failbit);
}
else {
student = std::move(local);
}
return stream;
}
};
//======================================================================
// sample_data
//======================================================================
std::string sample_data() {
return
R"sample("00072250018548","Dwayne "The Rock" Johnson","Wrestling",2.8
"00028000517205", "Beyonce" ,
"Music" ,
4.0
"00034000020706" ,
"Cristiano Ronaldo",
"Futebol",
4.0 "00038000570742",
"Kim Kardashian", "Undeclared",
1.9
"00014100072331" , "Oprah Winfrey", "Broadcast Journalism", 3.2
"00000000000000", "incomplete / invalid item"
)sample";
}
//======================================================================
// main
//======================================================================
int main()
{
std::istringstream iss{ sample_data() };
Student student;
while (iss >> student) {
std::cout << student << 'n';
}
return 0;
}
// end file: main.cpp
Output
Student ID : 00072250018548
Student name : Dwayne "The Rock" Johnson
Major : Wrestling
GPA : 2.8
Student ID : 00028000517205
Student name : Beyonce
Major : Music
GPA : 4.0
Student ID : 00034000020706
Student name : Cristiano Ronaldo
Major : Futebol
GPA : 4.0
Student ID : 00038000570742
Student name : Kim Kardashian
Major : Undeclared
GPA : 1.9
Student ID : 00014100072331
Student name : Oprah Winfrey
Major : Broadcast Journalism
GPA : 3.2
1