Trouble overloading >>

I created a simple program that gathers user input and stores it in an array of objects called Games. Then in the end, I go to output the contents of the array. I got my program to work gathering user input and storing it in the array, but I am now trying to do the same thing, but do it by overloading >> instead.

The problem I'm having is now that I overloaded >>, the program is not executing beyond the first prompt for user input. I know it might not be necessary to do this especially since I got the program to work properly before doing the overloading, but I'm trying to practice with operator overloading, specifically overloading the input and output operators. Any help with this would be appreciated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
 #include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class VideoGame
{
public:

	static int gameCount;
	VideoGame(){}

	explicit VideoGame(string& t, string& g, char& r, string& d) : //constructor
		title(t), genre(g), rating(r), developer(d)
	{
		gameCount++;
	}


	void setitle(const string& title) //Sets user input for title to corresponding data member
	{
		this->title = title;
	}

	string getTitle() //returns value of data member in question
	{
		return title;
	}

	void setGenre(const string& genre) //Sets user input for genre to corresponding data member
	{
		this->genre = genre;
	}

	string getGenre() //returns value of data member in question
	{
		return genre;
	}

	void setRating(const char& rating) //Sets user input for rating to corresponding data member
	{
		this->rating = rating;
	}

	char getRating() //returns value of data member in question
	{
		return rating;
	}

	void setDev(const string& dev) //Sets user input for developer to corresponding data member
	{
		this->developer = dev;
	}

	string getDev() //returns value of data member in question
	{
		return developer;
	}

	friend ostream& operator<< (ostream& out, VideoGame& game); //Function prototypes
	friend istream& operator>> (istream& in, VideoGame& game); 

private:
	string title;
	string genre;
	char rating;
	string developer;

}; //End VideoGame class

void printOpenMessage() //Function to print welcome message
{
	cout << endl;
	cout << "Welcome to the Video Game Inventory System" << endl;
	cout << setfill('%') << setw(50) << "%" << endl;
	cout << endl << endl;
}

ostream& operator<< (ostream& out, VideoGame& game) //Funstion to overload << to output array of objects
{
	out << "Title: " << setfill(' ') << setw(8) << " " << game.title << endl
		<< "Genre: " << setfill(' ') << setw(8) << " " << game.genre << endl
		<< "Rating: " << setfill(' ') << setw(7) << " " << game.rating << endl
		<< "Developer: " << setfill(' ') << setw(4) << " " << game.developer << endl;
	return out;
}

istream& operator>> (istream& in, VideoGame& game)
{
		in >> game.title
		   >> game.genre
		   >> game.rating
		   >> game.developer;

	return in;
}

void showstockMessage() //Function to print inventory message
{
	cout << endl;
	cout << "Here is what's in stock" << endl;
	cout << setfill('=') << setw(50) << "=" << endl;
	cout << endl << endl;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <string>
#include "VideoGame.h"
#include <iomanip>
using namespace std;

int main(void)
{

	VideoGame Games[5];
	//string info;
	//char rate;

	printOpenMessage(); //Prints welcome message
	
	for (int i = 0; i < 3; i++) //Uses for loop to gather user input for 3 games
	{
		cout << "Enter a game name: ";
		//getline(cin, info);
		//Games[i].setTitle(info);
		cin >> Games[i];
		cout << endl;

		cout << "Enter the game's genre: ";
		//getline(cin, info);
		//Games[i].setGenre(info);
		cin >> Games[i];
		cout << endl;

		cout << "Enter the games rating: ";
		//cin >> rate;
		//Games[i].setRating(rate);
		cin >> Games[i];
		cout << endl;

		cout << "Enter the games developer: ";
		cin.ignore();
		//getline(cin, info);
		//Games[i].setDev(info);
		cin >> Games[i];
		cout << endl;

} //End for

	showstockMessage(); //prints inventory results message
	
	for (int i = 0; i < 3; i++)
	{
		cout << Games[i] << endl; //Outputs all of the games that were entered in the array
	} //End for

	
return 0;

} //End main 
Could you explain exactly where the program stops executing? Give a line number, please.
I think I can guess what happened.
18
19
20
21
cout << "Enter a game name: ";
//getline(cin, info);
//Games[i].setTitle(info);
cin >> Games[i];

Line 18 runs and asks you to enter a game name.
Then the program reaches the cin >> Games[i]; line, which calls this:
88
89
90
91
92
93
94
95
96
istream& operator>> (istream& in, VideoGame& game)
{
		in >> game.title
		   >> game.genre
		   >> game.rating
		   >> game.developer;

	return in;
}

So now the program is expecting you to enter a title, and a genre, and a rating, and and developer.
You probably entered a title, and then wondered why your program wasn't continuing on when it was actually waiting for more input.

I suppose one way to implement your operator>> is to basically take your original lines 18-41 in the main file and cut/paste it into your operator>> function.
Then, just have your for loop be
16
17
18
19
for (int i = 0; i < 3; i++) //Uses for loop to gather user input for 3 games
{
    cin >> Games[i];
} //End for 

Although, I personally feel that you probably shouldn't have cout statements in a operator>> function, since I wouldn't normally expect a function whose sole purpose is to grab input to produce output at the same time.
Maybe it depends on the situation, though.
Eh, whatever....
> I got my program to work gathering user input and storing it in the array,
> but I am now trying to do the same thing, but do it by overloading >> instead.

This is not a good idea.

When we want to accept input typed in by the user on stdin, we would typically want to do it in a loop, with error recovery:

1
2
3
4
5
6
7
8
9
10
11
12
for each field that we want to read
        try: 
              ask the user to enter the field
              try to read the field
              if read successfully
                  continue to the next field
              else 
                  give an error message
                  clear error state of the stream 
                  go back to try to read the input again:

initialize the game object with the fields that were read


When we want to read data using operator >> (the input may be coming from a file), we expect the data to be in precisely the same format as was written out by operator<<, and fail the input in case of errors.

Ideally, we would want to write a separate function for to set the state of the object from input typed in by the user on stdin, and the overloaded operator >> to process input from any input stream (like a file stream or a socket stream).

Something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <iostream>
#include <string>
#include <sstream>

class VideoGame
{
    public:
        VideoGame() {}

        // we do not need references to mutable objects as input to the constructor
        // either pass by value or pass by reference to const (const std::string&)
        // should this multi-arg constructor be explicit?
        /*explicit*/ VideoGame( std::string t, std::string g, char r, std::string d) : //constructor
            title(t), genre(g), rating(r), developer(d) { /* ... */ }
        // ...

        // read values typed in by the user, with prompts and error recovery
        bool read_from_stdin() ; // TODO

        friend std::ostream& operator<< ( std::ostream& out, const VideoGame& game ); // const
        friend std::istream& operator>> ( std::istream& in, VideoGame& game ) ;

    private:
        std::string title; // may contain white space
        std::string genre; // may contain white space
        char rating;
        std::string developer; // may contain white space

        static const char title_tag[] ;
        static const char genre_tag[] ;
        static const char rating_tag[] ;
        static const char developer_tag[] ;

}; //End VideoGame class

const char VideoGame::title_tag[] = "Title:" ;
const char VideoGame::genre_tag[] = "Genre:" ;
const char VideoGame::rating_tag[] = "Rating:" ;
const char VideoGame::developer_tag[] = "Developer:" ;

std::ostream& operator<< ( std::ostream& out, const VideoGame& game )
{
    return out << VideoGame::title_tag << ' ' << game.title << '\n' // first line
                << VideoGame::genre_tag << ' ' << game.genre << '\n' // second line
                << VideoGame::rating_tag << ' ' << game.rating << '\n' // third line
                << VideoGame::developer_tag << ' ' << game.developer << '\n' ; // last line
}

// helper function to consume and validate tags
// skips white space on either side of the tag
static bool consume( std::istream& in, const char* expected )
{
    std::string str ;
    return in >> std::ws >> str >> std::ws && str == expected ;
}

std::istream& operator>> ( std::istream& in, VideoGame& game )
{
    std::string title ;
    std::string genre ;
    char rating ;
    std::string developer ;

    if( consume( in, VideoGame::title_tag ) &&
        std::getline( in, title ) &&
        consume( in, VideoGame::genre_tag ) &&
        std::getline( in, genre ) &&
        consume( in, VideoGame::rating_tag ) &&
        in >> rating &&
        consume( in, VideoGame::developer_tag ) &&
        std::getline( in, developer ) )
    {
        // read everything succesfully, set the state of the game object
        game = { title, genre, rating, developer } ;
    }

    else
    {
        // read failed
        in.setstate( std::ios::failbit ) ; // indicate input failure
        game = {} ; // optional: clear all the fields in the the game object
    }

    return in ;
}

int main()
{
     VideoGame ernie { "Alpha Beam With Ernie", "Edutainment", 'B', "Michael Callahan" } ;
     VideoGame dogs { "Nintendogs", "Pet Simulation", 'A', "Kiyoshi Mizuki" } ;

     std::stringstream stm ;
     stm << ernie << '\n' << dogs << '\n' ;
     std::cout << "written out\n-----------\n" << stm.str() ;

     VideoGame games[2] ;
     if( stm >> games[0] >> games[1] )
        std::cout << "read back\n-----------\n" << games[0] << '\n' << games[1] << '\n' ;
}

http://coliru.stacked-crooked.com/a/330ceab8abe31c7b
LB,

The program stops execution after line 18 is output, but after reading some of the other posts here, it's not really stopping, it's just waiting for more input.
JL Borges,

Thanks for the response. I always figured I should have error handling in my program but never put in the time to research how to do it yet. What I'm having some trouble understanding in your post is your consume function, and what it's supposed to be doing, as well as your overloaded >> function. Is the if statement starting on line 64 just reading user input, or is it somehow verifying that the correct user input was gathered? Also I see that you initialized VideoGame objects, but how would this work to have the objects initialized based off of user input.
> What I'm having some trouble understanding in your post is your consume function,
> and what it's supposed to be doing, as well as your overloaded >> function.

Let us say we have this object:
VideoGame ernie { "Alpha Beam With Ernie", "Edutainment", 'B', "Michael Callahan" } ;

And we write it to a file with: file_streamm << ernie << '\n' ;

Looking at our operator<< (), we can see that the text written into the file would be:
Title: Alpha Beam With Ernie
Genre: Edutainment
Rating: B
Developer: Michael Callahan

Now, the job of our operator>> () is to read in to an object, this text written out by our operator<< ().

The steps we have to perform are:
1
2
3
4
5
6
7
8
    a. verify that the first line starts with the tag "Title:"
    b. if yes, read the rest of the line, after leaving a space, to get the title    
    c. verify that the second line starts with the tag "Genre:"
    d. if yes, read the rest of the line, after leaving a space, to get the genre    
    e. verify that the third line starts with  the tag "Rating:"
    f. if yes, read the character, after leaving a space, to get the rating   
    g. verify that the last line starts with  the tag "Developer:"
    h. if yes, read the rest of the line, after leaving a space, to get the developer

If all the above steps are successful, modify the object with the information that has been read
Otherwise, indicate that an error occurred by putting the stream into a failed state.

If we look at the above, we can see that steps a, c, e and g have a lot in common; the only variance is that the tags are different for the different steps.

This common task is what static bool consume( std::istream& in, const char* expected ) does.
As input, it is given the stream and the expected tag.
If the expected tag is found, it 'consumes' the tag (extracts it, removes it from the stream) and returns true.
If it is not found, it returns false.
1
2
3
    std::string str ; 
    // try to read (consumes) the next string, and return true if it is the expected tag 
    return in >> std::ws >> str >> std::ws && str == expected ;

The only other thing it does is to skip (or consume) the space(s) immediately after the tag with >> std::ws. This is something we have to do after each tag and it is convenient to have our consume function do it for us.

Once this much is understood, operator>> is a breeze:
1
2
3
4
5
6
7
8
if( consume( in, VideoGame::title_tag ) && // if we have successfully consumed the tag "Title:"
    std::getline( in, title ) && // and we have successfully read the rest of the line into title
    consume( in, VideoGame::genre_tag ) && // and we have successfully consumed the tag "Genre:"
    std::getline( in, genre ) && // and we have successfully read the rest of the line into genre
    consume( in, VideoGame::rating_tag ) && // and we have successfully consumed the tag "Rating:"
    in >> rating && // and we have successfully read the next character into rating
    consume( in, VideoGame::developer_tag ) && // and we have successfully consumed the tag "Developer:"
    std::getline( in, developer ) ) // and we have successfully read the rest of the line into developer 

we have read all the information correctly, update the object with the data that was read
1
2
3
4
{
    // read everything succesfully, set the state of the game object
    game = { title, genre, rating, developer } ;
}

if not, our attempt to read has failed, indicate failure by putting the stream into a failed state
1
2
3
4
5
6
else
{
    // read failed
    in.setstate( std::ios::failbit ) ; // indicate input failure
    game = {} ; // optional: clear all the fields in the the game object
}



> Is the if statement starting on line 64 just reading user input,
> or is it somehow verifying that the correct user input was gathered?

Our operator>>() does not attempt to read input typed in by the user; it attempts to read what was written out by our operator<<()


> Also I see that you initialized VideoGame objects,
> but how would this work to have the objects initialized based off of user input.

That part was left undone in the code originally posted.
1
2
// read values typed in by the user, with prompts and error recovery
bool read_from_stdin() ; // TODO 


Let us take it one step at a time; this is a fair amount to digest at one time. Try to understand what has been done in operator>>(). Once we are clear with that, we can take up the next question: how would we accept input typed in by the user, and initialize an object with that input, handling errors in input, giving feedback to the user, prompting for re-entry of incorrect information etc.
JL Borges,

Thanks for the detailed response and taking the time to explain this to me. That's a good amount of information your post covers and I'm still trying to understand it all but I'm still a little bit confused here. I believe I get the gist of what the consume() and the operator>>() do. I'm just trying to wrap my head around how they go about doing it.

I believe that when the consume() is called, it determines if the correct tag is being used, and if so, the getline() is used next to read the input, and store it in the correct data member. It does this for all of the data members of the object in question, title, genre, rating and developer, and if, and only if, all the calls to the consume() come back as true, then this information is stored in the object. Otherwise if one of the function calls to consume() come back as false, then it indicates this, and clears all of the data members for that object.

One question I had was in the consume(). For the parameters in the function, I'm assuming that in is supposed to represent, for example the title of the game, and the parameter expected is being used to represent the expected tag. I see that you first create a string called str in the body of the function, and then use it to determine if the correct tag is being used. What exactly is str being used to represent.

The line return in >> ws >> str >> ws && str == expected;, I'm still not 100% sure as to how that line verifies that the correct tag is being used. It looks like ws is being used to read the spaces before and after str. Is str representing the expected tag?

I know that once I can grasp how the consume() verifies the tag being used, the rest of the operator>>() will come easier.

> when the consume() is called, it determines if the correct tag is being used

Yes.

> if, and only if, all the calls to the consume() come back as true, then this information is stored in the object.

Yes.

> Otherwise if one of the function calls to consume() come back as false, then it indicates this,
> and clears all of the data members for that object.

Yes. It indicates failure by puting the stream into a failed state: setstate( std::ios::failbit ) ;

> the parameter expected is being used to represent the expected tag.

Yes.

> in is supposed to represent, for example the title of the game

No, in is the input stream from where data is being read.


> What exactly is str being used to represent.
> Is str representing the expected tag?

Yes. str is used to hold the actual tag read from the stream. It is then compared with expected. If they compare equal, the actual tag in the stream is the expected tag (return true).


> The line return in >> ws >> str >> ws && str == expected;

That line does several things, one after the other.

Let us isolate the consume() function from the rest of the code, break up complex constructs into separate small individual steps, and put in some debugging scaffolding. This would help us test, debug, and eventually understand what the code is doing.

Run the code below, and examine the output.

Play around with it - modify the code to use different character sequences in the input stream, and different expected tags. Run the modified code, and examine the output.

If things have not yet become completely clear, we can step through the code with a debugger. By now, we would at least know what questions we should be asking the debugger. (Without having first gained that knowledge, without having prepared the code for debugging, extended use of a debugger is actually counter-productive.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <iostream>
#include <sstream>
#include <string>

void show_get_area_of_stream( std::istringstream& input_stream, const char* desc )
{
    std::cout << "get area " << desc << ": " ;

    // tellg gives us the current get position in the stream
    const auto get_position = input_stream.tellg() ;

    // print out the contents of the underlying string from there onwards
    // (that is what would actually be read by the next input operation)
    constexpr char quote = '"' ;
    std::cout << quote << input_stream.str().substr(get_position) << quote << '\n' ;

}

// helper function to consume and validate tags
// skips white space on either side of the tag
// the parameter 'in' has been renamed as 'input_stream' (to make the intent clear)
// the type of the stream has been changed to a string stream
// (istringstream makes it easy for us to look at the get area of the stream
//  and understand what is going on under the hood; we will leave it as an
// istream (to handle any kind of input stream) in the original code
static bool consume( std::istringstream& input_stream, const char* expected )
{
    std::string str ;

    // what followes is an elaborated version of the original
    // return input_stream >> std::ws >> str >> std::ws && str == expected ;
    // with scaffolding:
    
    // each step is separated out so that it can be viewed on its own,
    // stepped through incrementally in a debugger,
    // with comments annotating what each step does
    // and diagnostic messages printed out after each step to give us an 
    // easily reproducible, persistent record of what went on

    show_get_area_of_stream( input_stream, "on entry" ) ;
    std::cout << "expected tag: \"" << expected << "\"\n" ; ;

    // skip leading whitespace
    // (this step is really not essential right now, because 'expected'
    //  is a string that does not contain embedded white spaces)
    bool ok = ( input_stream >> std::ws ).good() ; // ok is false on input failure
    if( !ok ) return false ; // failed, return false (expected tag was not consumed)
    show_get_area_of_stream( input_stream, "after input_stream >> std::ws" ) ;

    // try to read everything upto the next whitespace into str
    ok = ( input_stream >> str ).good() ; // ok is false on input failure
    if( !ok ) return false ; // failed, return false (expected tag was not consumed)
    show_get_area_of_stream( input_stream, "after input_stream >> str" ) ;
    std::cout << "str now contains: \"" << str << "\"\n" ; ;

    // skip trailing whitespaces immediately after the string that was read
    ok = ( input_stream >> std::ws ).good() ; // ok is false on input failure
    if( !ok ) return false ; // failed, return false (expected tag was not consumed)
    show_get_area_of_stream( input_stream, "after another input_stream >> std::ws" ) ;

    std::cout << "actual string read: \"" << str << "\"\n" ; ;
    std::cout << "expected tag: \"" << expected << "\"\n" ; ;

    // if str is the expected tag, we have consumed the expected tag successfully
    bool got_expected_tag = ( str == expected ) ;
    std::cout << "did we get the expected tag? "
              << ( got_expected_tag ? "yes" : "no" ) << '\n' ;

    return got_expected_tag ;
}

int main()
{
    {
        std::istringstream stm( " Title: Alpha Beam With Ernie" ) ;
        bool result = consume( stm, "Title:" ) ;
        std::cout << "consume returned " << std::boolalpha << result << '\n' ;
        show_get_area_of_stream( stm, "after consume( stm, \"Title:\" )" ) ;
    }

    std::cout << "\n---------------------------\n\n" ;

    {
        std::istringstream stm( " Blah: Alpha Beam With Ernie" ) ;
        bool result = consume( stm, "Title:" ) ;
        std::cout << "consume returned " << std::boolalpha << result << '\n' ;
        show_get_area_of_stream( stm, "after consume( stm, \"Title:\" )" ) ;
    }

    std::cout << "\n---------------------------\n\n" ;

    // putting it together now:
    {
        std::istringstream stm( " Title: Alpha Beam With Ernie\n Genre: ...blah..." ) ;
        show_get_area_of_stream( stm, "at start" ) ;

        std::string title ;

        // we already know what goes on inside consume
        // so lets just silence std::cout for the duarion of consume()
        std::cout.clear(std::ios::failbit) ;

        if( consume( stm, "Title:" ) &&
            std::getline( stm, title ) )
        {
            std::cout.clear() ; // unsilence std::cout
            std::cout << "consume() returned true\n"
                      << "std::getline( stm, title ) returned true\n"
                      << "title now contains: \"" << title << "\"\n" ;
            show_get_area_of_stream( stm, "after reading title" ) ;
        }
    }
}

http://coliru.stacked-crooked.com/a/80c5cce0108e969e
JL Borges,

Ok, so I think I might have a better understanding of this now. Thanks for breaking that consume() up so that I can get a better idea of what is going on in each section of it. It's actually making more sense to me now. I'm able to, for the most part, determine what the correct output in each section should be, as well as what happens during each function call based on the code. For example in this line show_get_area_of_stream( input_stream, "on entry" ) ;, the output should be the entire input stream, including the tag, such as
title: Halo


Each section of the consume() is basically in charge of either extracting the tag from the input stream, determining the expected tag, or extracting the rest of the input stream that comes after the tag.

So in line std::cout << "actual string read: \"" << str << "\"\n" ; ;, this should result in the output of the actual tag that was read from the input stream, such as
title:


The line std::cout << "expected tag: \"" << expected << "\"\n" ; ;, should have the output of the expected tag that should be used. So str is referring to the actual tag that was extracted from the input stream, and in or input_stream is referring to the entire stream that was read.

I'm going to play around with this code and try different inputs and games until I get better acquainted with everything in it, and what everything does, then I can move on to actually using user input for the game information instead of initializing the games on their creation.
> Each section of the consume() is basically in charge of either extracting the tag from the input stream,
> determining the expected tag,
> or extracting the rest of the input stream that comes after the tag.

consume() extracts the actual tag present in the input stream (ino str).

It does not determine what the expected tag should be; it is given expected as an input parameter; it is told what the expected tag is. It determines what the actual tag extracted is (str) and checks if the actual tag extracted is the same as the expected tag (expected)

It also does not extract everything in the rest of the input stream that comes after the tag. It extracts only the white space characters that are immediately after the tag, and leaves the rest in the stream. The actual title is extracted from the stream after consume returns. In the last example above:
1
2
consume( stm, "Title:" ) && // consume extracts the tag "Title:", and a space 
std::getline( stm, title ) // getline() extracts the title "Alpha Beam With Ernie" 


Stay with it for a while, try different inputs, and it will become very clear.
Last edited on
JL Borges,

Thanks again for your help with this. I've been teaching myself C++ for about 2 months now and some things like this have been challenging for me to really grasp, but I'm sure that in time and with practice, this stuff will come much easier. I'll keep practicing with that code you gave me and in time, I'm sure it will be much clearer.
OK. Now see what you can make of this. Code to accept user input and create a videogame object from that has been added. (Input validation is merely for exposition; you would have to modify them for as per your actual validation requirements).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include <iostream>
#include <string>
#include <sstream>
#include <cctype>

class VideoGame
{
    public:
        VideoGame() {}

        // we do not need references to mutable objects as input to the constructor
        // either pass by value or pass by reference to const (const std::string&)
        // should this multi-arg constructor be explicit?
        /*explicit*/ VideoGame( std::string t, std::string g, char r, std::string d) : //constructor
            title(t), genre(g), rating(r), developer(d) { /* ... */ }
        // ...

        friend std::ostream& operator<< ( std::ostream& out, const VideoGame& game ); // const
        friend std::istream& operator>> ( std::istream& in, VideoGame& game ) ;

    private:
        std::string title; // may contain white space
        std::string genre; // may contain white space
        char rating;
        std::string developer; // may contain white space

        static const char title_tag[] ;
        static const char genre_tag[] ;
        static const char rating_tag[] ;
        static const char developer_tag[] ;

}; //End VideoGame class

const char VideoGame::title_tag[] = "Title:" ;
const char VideoGame::genre_tag[] = "Genre:" ;
const char VideoGame::rating_tag[] = "Rating:" ;
const char VideoGame::developer_tag[] = "Developer:" ;

std::ostream& operator<< ( std::ostream& out, const VideoGame& game )
{
    return out << VideoGame::title_tag << ' ' << game.title << '\n' // first line
                << VideoGame::genre_tag << ' ' << game.genre << '\n' // second line
                << VideoGame::rating_tag << ' ' << game.rating << '\n' // third line
                << VideoGame::developer_tag << ' ' << game.developer << '\n' ; // last line
}

// helper function to consume and validate tags
// skips white space on either side of the tag
static bool consume( std::istream& in, const char* expected )
{
    std::string str ;
    return in >> std::ws >> str >> std::ws && str == expected ;
}

std::istream& operator>> ( std::istream& in, VideoGame& game )
{
    std::string title ;
    std::string genre ;
    char rating ;
    std::string developer ;

    if( consume( in, VideoGame::title_tag ) &&
        std::getline( in, title ) &&
        consume( in, VideoGame::genre_tag ) &&
        std::getline( in, genre ) &&
        consume( in, VideoGame::rating_tag ) &&
        in >> rating &&
        consume( in, VideoGame::developer_tag ) &&
        std::getline( in, developer ) )
    {
        // read everything succesfully, set the state of the game object
        game = { title, genre, rating, developer } ;
    }

    else
    {
        // read failed
        in.setstate( std::ios::failbit ) ; // indicate input failure
        game = {} ; // optional: clear all the fields in the the game object
    }

    return in ;
}

//////////////////////////////////////////////////////////////////////////////
/////////////  this is the part that has been added //////////////////////////
//////////////////////////////////////////////////////////////////////////////

// return trimmed (leading and trailing spaces removed) string
std::string trim( const std::string& str )
{
    static const std::string ws = " \t\n" ;
    auto first = str.find_first_not_of(ws) ;
    auto last = str.find_last_not_of(ws) ;
    return first == std::string::npos ? "" : str.substr( first, last-first+1 ) ;
}

// returns the count of alphanumeric characters in a string
std::size_t count_alnum( const std::string& str )
{
    std::size_t n = 0 ;
    for( char c : str ) if( std::isalnum(c) ) ++n ;
    return n ;
}

// prompts the user to enter a string with at least min_chars alphanumeric
// reads the string with validation and retries
// returns validated input
std::string get_string( const char* prompt, std::size_t min_alnum = 0 )
{
    // prompt for input
    std::cout << prompt << ' ' ;
    if( min_alnum ) std::cout << " (min " << min_alnum << " letters or digits): " ;

    // accept input
    std::string text ;
    std::getline( std::cin, text ) ;

    // validate input, return sanitized input if valid
    if( count_alnum(text) >= min_alnum ) return trim(text) ;

    // validation failed, inform the user
    std::cout << "invalid input (too few letters or digits); please re-enter\n" ;

    // clear error state, clean up input buffer (not required in this case)
    // std::cin.clear() ;
    // std::cin.ignotre( 10000, '\n' ) ;

    return get_string( prompt, min_alnum ) ;
}

VideoGame video_game_from_user_input()
{
    // validation tules:
    // title must have at least 5 letters or digits, may contain spaces
    // genre must have at least 3 letters or digits, may contain spaces
    // rating must be one of ABCDE
    // developer can be an empty string, may contain spaces

    const std::string title = get_string( "game title?", 5 ) ; // min 5 chars
    const std::string genre = get_string( "genre?", 3 ) ; // min 3 chars

    char rating = ' ' ;
    static const std::string valid_ratings = "ABCDE" ;
    bool done = false ;
    while( !done )
    {
        std::cout << "game rating? (one of " << valid_ratings << "): " ;
        std::cin >> rating ;
        rating = std::toupper(rating) ;
        done = valid_ratings.find(rating) != std::string::npos ;
        if(!done) std::cout << "rating must be one of: " << valid_ratings << '\n' ;
    }
    // formatted input std::cin >> rating ; would have left a new line
    // in the input buffer; extract it and throw it away
    std::cin.ignore( 1000, '\n' ) ;

    const std::string developer = get_string( "developer?" ) ; // may be empty

    return VideoGame( title, genre, rating, developer ) ;
}

int main()
{
    VideoGame a = video_game_from_user_input() ;
    std::cout << '\n' << a << '\n' ;

    std::stringstream stm ;
    stm << a << '\n' ; // write a to stream

    VideoGame b ;
    stm >> b ; // read back from stream into b
    std::cout << b << '\n' ;
}
Last edited on
Topic archived. No new replies allowed.