Part 8 of Person drill for PPP2 Chapter 15

I'll make a new thread for this here.

Edit: Now I need help with number 8, "Read a sequence of Persons from input (cin) into a vector<Person>; write them out again to the screen (cout). Test with correct and erroneous input."

Here's my code:
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
// Osman Zakir
// 6 / 23 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 15 Class definition drill
// Drill Specifications:
/**
* 1. Define a struct Person containing a string name and an int age.
* 2. Define a variable of type Person , initialize it with “Goofy” and 63, and
* write it to the screen ( cout ).
* 3. Define an input ( >> ) and an output ( << ) operator for Person ; read in a
* Person from the keyboard ( cin ) and write it out to the screen ( cout ).
* 4. Give Person a constructor initializing name and age .
* 5. Make the representation of Person private, and provide const member
* functions name() and age() to read the name and age.
* 6. Modify >> and << to work with the redefined Person .
* 7. Modify the constructor to check that age is [0:150) and that name doesn’t
* contain any of the characters ; : " ' [ ] * & ^ % $ # @ ! . Use error() in case
* of error. Test.
* 8. Read a sequence of Person s from input ( cin ) into a vector<Person> ;
* write them out again to the screen ( cout ). Test with correct and errone-
* ous input.
* 9. Change the representation of Person to have first_name and second_name
* instead of name . Make it an error not to supply both a first and a second
* name. Be sure to fix >> and << also. Test.
*/

#include "../../cust_std_lib_facilities.h"
#include <iostream>
#include <vector>
#include <string>

struct Person
{
	Person(const std::string &name, const int age);
	Person();
	std::string name() const { return m_name; }
	int age() const { return m_age; }
	void set_name(const std::string &name) { m_name = name; }
	void set_age(const int age) { m_age = age; }
private:
	std::string m_name;
	int m_age;
};

std::ostream &operator<<(std::ostream &os, const Person &person);
std::istream &operator>>(std::istream &is, Person &person);
void fill_person_v(std::vector<Person> &persons_v);
void print_person_v(const std::vector<Person> &persons_v);

int main()
{
	using namespace std;

	try
	{
		vector<Person> persons_v;
		fill_person_v(persons_v);
		print_person_v(persons_v);
	}
	catch (const runtime_error &rte)
	{
		cerr << "Runtime error: " << rte.what() << '\n';
	}
	catch (const out_of_range &oor)
	{
		cerr << "Out of range error: " << oor.what() << '\n';
	}

	keep_window_open();
}

Person::Person(const std::string &name, const int age)
	:m_name{ name }, m_age{ age }
{
	if (age < 0 || age > 150)
	{
		error("age out of range");
	}

	std::string bad_chars{ ":;''""[]*&^%$#@!" };
	for (std::size_t i = 0; i < m_name.size(); ++i)
	{
		if (m_name.find_first_of(bad_chars[i]))
		{
			error("name consists of invalid characters");
		}
	}
}

Person::Person()
	: m_name{}, m_age{}
{
}

std::ostream &operator<<(std::ostream &os, const Person &person)
{
	return os << person.name() << " -- age: " << person.age();
}

std::istream &operator>>(std::istream &is, Person &person)
{
	using namespace std;

	string name;
	getline(is, name, '\n');
	int age;
	is >> age;

	person = Person::Person{ name, age };

	return is;
}

void fill_person_v(std::vector<Person> &persons_v)
{
	using namespace std;
	cout << "Enter some people's name and age (separate name and age by 'Enter' keypress, please)\n"
		<< "end sequence of persons by entering '~'\n";
	for (Person person; cin >> person;)
	{
		char ch;
		cin.get(ch);
		if (ch != '~')
		{
			cin.putback(ch);
			persons_v.push_back(person);
		}
		else
		{
			cout << "Sequence ended\n";
			break;
		}
	}
}

void print_person_v(const std::vector<Person> &persons_v)
{
	using namespace std;
	for (std::size_t i = 0; i < persons_v.size(); ++i)
	{
		cout << persons_v.at(i) << '\n';
	}
}


I get the "invalid characters in name" error whatever name I enter. What am I doing wrong?
Dragon – first and foremost good to see you've completed your graphics lessons, now I hope we'll all move on to the REALLY exciting stuff together like these, concurrency, template meta prog et all …
anyways, back to your post – (a) you don't actually describe what should happen within the overloaded ctor in case the input is fine, (b) you'd also need the keyword throw in this ctor overload to propagate the error out of the enclosing try block in main() so that it can be caught within the subsequent catch block:
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
# include <iostream>
# include <string>
# include <stdexcept>

struct Person
{
    std::string m_name;

    Person (const std::string name)
    {
        std::string badChars = "\":;''[]*&^%$#@!";
        //note escape character before "
        auto found = name.find_first_of(badChars);
        if (found == std::string::npos)
        {
            m_name = name;
        }
        else
        {
            throw std::invalid_argument("name is not valid \n");
        }
    }
};

int main()
{
    try
    {
        Person p("john");
        std::cout << "person obj created with name " << p.m_name << "\n";
        Person q("john!");
        std::cout << "person obj created with name " << q.m_name << "\n";
    }
    catch (const std::invalid_argument & e)
    // could also be std::exception & (grandparent class) or std::logic_error & (parent class)
    {
        std::cerr << e.what();
    }
}
Last edited on
The keyword throw is used in error(), isn't it? So do I really need to use it explicitly? Or do you not remember what's going on in error()?

1
2
3
4
5
6
7
8
9
10
void error(const std::string& s)
{
	using namespace std;
	throw runtime_error(s);
}

void error(const std::string& s, const std::string& s2)
{
	error(s + s2);
}


But yeah, it's runtime_error and not invalid_argument.

As for what should happen in case the input is fine, that's what the initializer after the colon is for, isn't it?
1
2
Person::Person(const std::string &name, const int age)
	:m_name{ name }, m_age{ age }


The :m_name{ name }, m_age{ age } line should be telling the constructor what to do in case of valid input, right? Or do I still need to explicit within the constructor body?

Edit: Okay, I made the changes and now it works:
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
// Osman Zakir
// 6 / 23 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 15 Class definition drill
// Drill Specifications:
/**
* 1. Define a struct Person containing a string name and an int age.
* 2. Define a variable of type Person , initialize it with “Goofy” and 63, and
* write it to the screen ( cout ).
* 3. Define an input ( >> ) and an output ( << ) operator for Person ; read in a
* Person from the keyboard ( cin ) and write it out to the screen ( cout ).
* 4. Give Person a constructor initializing name and age .
* 5. Make the representation of Person private, and provide const member
* functions name() and age() to read the name and age.
* 6. Modify >> and << to work with the redefined Person .
* 7. Modify the constructor to check that age is [0:150) and that name doesn’t
* contain any of the characters ; : " ' [ ] * & ^ % $ # @ ! . Use error() in case
* of error. Test.
* 8. Read a sequence of Person s from input ( cin ) into a vector<Person> ;
* write them out again to the screen ( cout ). Test with correct and errone-
* ous input.
* 9. Change the representation of Person to have first_name and second_name
* instead of name . Make it an error not to supply both a first and a second
* name. Be sure to fix >> and << also. Test.
*/

#include "../../cust_std_lib_facilities.h"
#include <iostream>
#include <vector>
#include <string>

struct Person
{
	Person(const std::string &name, const int age);
	Person();
	std::string name() const { return m_name; }
	int age() const { return m_age; }
	void set_name(const std::string &name) { m_name = name; }
	void set_age(const int age) { m_age = age; }
private:
	std::string m_name;
	int m_age;
};

std::ostream &operator<<(std::ostream &os, const Person &person);
std::istream &operator>>(std::istream &is, Person &person);
void fill_person_v(std::vector<Person> &persons_v);
void print_person_v(const std::vector<Person> &persons_v);

int main()
{
	using namespace std;

	try
	{
		vector<Person> persons_v;
		fill_person_v(persons_v);
		print_person_v(persons_v);
	}
	catch (const runtime_error &rte)
	{
		cerr << "Runtime error: " << rte.what() << '\n';
	}
	catch (const out_of_range &oor)
	{
		cerr << "Out of range error: " << oor.what() << '\n';
	}

	keep_window_open();
}

Person::Person(const std::string &name, const int age)
	:m_name{ name }, m_age{ age }
{
	if (age < 0 || age > 150)
	{
		error("age out of range");
	}
	else
	{
		m_age = age;
	}

	std::string bad_chars{ "\"\':;[]*&^%$#@!" };
	auto found = m_name.find_first_of(bad_chars);
	if (found != std::string::npos)
	{
		error("name is invalid");
	}
	else
	{
		m_name = name;
	}
}

Person::Person()
	: m_name{}, m_age{}
{
}

std::ostream &operator<<(std::ostream &os, const Person &person)
{
	return os << person.name() << " -- age: " << person.age();
}

std::istream &operator>>(std::istream &is, Person &person)
{
	using namespace std;

	string name;
	getline(is, name, '\n');
	int age;
	is >> age;
	cin.ignore();

	person = Person::Person{ name, age };

	return is;
}

void fill_person_v(std::vector<Person> &persons_v)
{
	using namespace std;
	cout << "Enter some people's name and age (separate name and age by 'Enter' keypress, please)\n"
		<< "end sequence of persons by entering '~'\n";
	for (Person person; cin >> person;)
	{
		char ch;
		cin.get(ch);
		if (ch != '~')
		{
			cin.putback(ch);
			persons_v.push_back(person);
		}
		else
		{
			cout << "Sequence ended\n";
			break;
		}
	}
}

void print_person_v(const std::vector<Person> &persons_v)
{
	using namespace std;
	for (std::size_t i = 0; i < persons_v.size(); ++i)
	{
		cout << persons_v.at(i) << '\n';
	}
}


As for the graphics stuff, it's still there in Chapter 15. Some of the exercises are also about graphics, and some of the drill also involves graphics (I did that part already). Chapter 15 is on graphing functions and it also talks about lambda expressions. Though I had to learn the hard way that capturing lambdas can't be used as the first argument to the Function constructor since you can't use it as a function pointer. The book uses a capturing lambda, but my compiler wouldn't let me do it so I had to find an alternative.

In Chapter 10, he explicitly says, "The graphical output and graphical user interactions are served by a vari-
ety of different libraries, and we will focus on that kind of I/O in Chapters 12 to 16.". So the graphics stuff apparently stays until Chapter 16.
Last edited on
But yeah, it's runtime_error and not invalid_argument.


Logic errors are errors that, at least in theory, could be avoided by the program; for example, by performing additional tests of function arguments … Exceptions derived from runtime_error are provided to report events that are beyond the scope of a program and not easily avoidable.
- “The C++ Standard Library” (2nd ed), N Josuttis (Chapter 4.3)

edit: also in the version of your program that works lines 90-93 are no longer required given member initialization
Last edited on
The exercise doesn't demand it, but it would be better if you check the input int your set_name and set_age function as well.
Well, the exercise does only ask to provide getters for name and age. Providing setters was my own idea because I like having both setters and getters. But yeah, I'll try doing that. Thanks for the advice.

By the way, given the fact that it says that GUI I/O is in Chapters 12 - 16, how come there's been no actual I/O for GUI (read: cin/cout equivalents for GUI) even up until Chapter 15? Will it be in Chapter 16? I still don't know how to do GUI I/O.

Anyway, I'm done with the drill now and have to think about the review questions next and then do the exercises. Wish me luck, guys. I'll ask if I need help. Though it doesn't look like help on Exercise 1. Exercise 2, maybe (it's the one that asks to define a version of Function that would store its arguments (the Function provided by Dr. Stroustrup's GUI interface library stores the arguments in its Shape part only). I'm not sure how I'll store them, but I'll try something when I get to it.
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
#include <iostream>
#include <vector>
#include <string>

void error(std::string) {}

struct Person
{
	Person( const std::string &name, int age )
	    : m_name( validated_name(name) ), m_age( validated_age(age) ) {}
	Person() = default ; // note: default member-initialiser initialises age to zero

	std::string name() const { return m_name; }
	int age() const { return m_age; }

	void name( const std::string& name ) { m_name = validated_name(name) ; }
	void age( int age) { m_age = validated_age(age) ; }

    private:

	    std::string m_name;
	    int m_age = 0 ;

	    const std::string& validated_name( const std::string& name ) ;
	    int validated_age( int age ) ;
};

const std::string& Person::validated_name( const std::string& name )
{
    static const std::string bad_chars{ "\"\':;[]*&^%$#@!" };
    if( name.find_first_of(bad_chars) ) error( "bad name '" + name + '\'' ) ;
    return name ;
}

int validated_age( int age )
{
    // check that age is [0:150) ie. 0 <= age && age < 150
    if( age < 0 || age > 149 ) error( "bad age " + std::to_string(age) ) ;
    return age ;
}

// ... 
Topic archived. No new replies allowed.