Tag Archives: C++

Accelerated C++ Solution to Exercise 5-0 (Part 2 / 3)

This is Part 2 of the 3-part Solution to Exercise 5-0.

Exercise 5-0 (Part 2 / 3)

This is a very simple program with the purpose of splitting a line of text (i.e. a input string) into words (i.e. an output vector<string>).

The Problem

Start from scratch, we need to write a program that is able to read in a line of text, and be able to split it into words. The full description and explanation of the program can be found in Chapter 5 of the book.

Solution Strategy

The text book sample program for this (part 2) exercise is fairly simple. It only requires 3 files which I can group into a small project as usual – I will describe this in the Project section below.

There is also one core learning from this chapter regarding sequential container processing using either (1) index, or (2) iterator. Both methods are effectively the same. e.g. assuming v is a vector<string>, the two following codes have the same effect (of displaying container elements to the output console).

Sequential Container Processing with Index:

        for (vector<string>::size_type i = 0; i != v.size(); ++i)
            cout << v[i] << endl;

Sequential Container Processing with Iterator:

        for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
            cout << (*iter) << endl;

Remark

As the Author mentioned in the book, however:

  • Index is good for random container processing, whereas
  • Iterator is good for sequential container processing.

It is always a good idea to bear this in mind.

The Project

This is what the management tree looks like in Code::Block:

Acpp5p0p2MgntTree

C++ Source Files

  • main.cpp – this is the first program that is run during the implementation phase.
  • split.cpp – contains the split function.

C++ Header Files

  • split.h – declare the functions in the source file split.cpp.

Source Files

main.cpp

#include <iostream>  // cin, cout, endl, getline
#include <vector>    // vector
#include <string>    // string
#include "split.h"   // split

using std::vector;
using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::getline;

// (5.7/90)
int main()
{
    string s;

    // read and split each line of input
    while (getline(cin, s))
    {
        vector<string> v = split(s);

        // write each word in v
        for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
            cout << (*iter) << endl;
    }
    return 0;
}

split.cpp

#include <string>    // string
#include <vector>    // vector
#include <cctype>    // isspace

using std::vector;
using std::string;
using std::isspace;

// scan a string of texts, split into words, return a vector that contains these words.
// (S5.6/88)
vector<string> split(const string& s)
{
    vector<string> ret;
    typedef string::size_type string_size;
    string_size i = 0;

    // invariant: we have processed characters original value of [i, i)
    while (i != s.size() )
    {
        // ignore leading blanks
        // invariant: characters in range [original i, current i)
        while (i != s.size() && isspace(s[i]))
            ++i;

        // find end of next word
        string_size j = i;

        // invariant: none of the characters in range [original j, current j) is a space
        while (j != s.size() && !isspace(s[j]))
            ++j;

        // if we found some non-whitespace characters
        if (i != j)
            // copy from s starting at i and taking j - i chars
            ret.push_back(s.substr(i, j - i));
            i = j;
    }
    return ret;
}

Header Files

split.h

#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

std::vector<std::string> split(const std::string&);

#endif // GUARD_SPLIT_H

Test Results

I am going to enter the following 3 lines when the program starts up. I should see the individual words (separated by empty space) displayed on the screen straight after I hit the enter button.

  1. Hello world, how are we today?
  2. Nice, so the program works!
  3. Bye bye
Hello world, how are we today?
Hello
world,
how
are
we
today?
Nice, so the program works!
Nice,
so
the
program
works!
Bye bye
Bye
bye
^Z

The program works as expected!

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 5-0 (Part 1 / 3)

This is Part 1 of the 3-part Solution to Exercise 5-0.

Exercise 5-0 (Part 1 / 3)

We extend the Solution to Exercise 4-0 (i.e. the student grading program) to include a capability that split the set of students (objects) into two groups – namely the passing students and failing students. This capability is enabled by a new “home-made” function called extract_fails. This function can be written in 4 different versions in which all 4 versions deliver the same result, with the slight difference in terms of efficiency. It goes from version 1 being the most primitive / inefficient, to version 4 being the most elegant / efficient. We will describe and test out each version one-by-one.

The Problem

The objective of this exercise is to enhance the Chapter 4 program so that we are able to determine, out of the entire Student_info object population, which ones belong to the passing group, and which ones belong to the failing group.

The Solution Strategy

There are many good ways to implement this Chapter 5 example program. Here is my strategy:

  1. Make a copy of the entire Chapter 4 project. i.e. the Project as per Solution to Exercise 4-0.
  2. Add a C++ source file extract_fails.cpp. It contains the 4 versions of the extract_fail function. To make it easy to test later, I simply create 4 functions. Namely: extract_fails_v1, extract_fails_v2, extract_fails_v3, extract_fails_v4. The function fgrade is also stored in this file.
  3. Add a corresponding C++ header file. i.e. extract_fails.h.
  4. Adjust the main function to make it suitable for testing. Note that extract_fail version 1, 2 and 3 use vector<Student_info> container, whereas version 4 uses list<Student_info> container. Because of this, I will have two versions of main program for the testing – i.e. no1_main() for testing out extract_fail version 1, 2, 3. no2_main() for testing out extract_fail version 4.

The Project

I partition the program into smaller chunks – i.e. in C++ source files and header files. This diagram below shows what the Code::Block Management Tree look like after successful creation of these files.

Acpp5p0p1MgntTree

Note that majority of these files are straight copies of the Chapter 4 Program. (i.e. Solution to Exercise 4-0). (the ones that are NOT circled in red)

In this exercise we add these files (circled in red as per snapshot diagram above): extract_fails.cpp, extract_fails.h. We also amend the main.cpp accordingly so that we are able to test out the newly written functions using tailored main program.

C++ Source Files

  • main.cpp – (Adjust accordingly) this is the first program that is run during the implementation phase.
  • extract_fails.cpp – (New!) contains the fgrade function, and the 4 versions extract_fail functions.
  • grade.cpp – (From Chapter 4) contains all functions relating to computing grades.
  • median.cpp – (From Chapter 4) contains all functions relating to computing median.
  • Student_info.cpp – (From Chapter 4) contains all functions relating to handling a Student_info object.

C++ Header Files

  • extract_fails.h – (New!) declare the functions as defined in extract_fails.cpp.
  • grade.h – (From Chapter 4) declare the functions as defined in grade.cpp.
  • median.h – (From Chapter 4) declare the functions as defined in median.cpp
  • Student_info.h – (From Chapter 4) declare the functions as defined in Student_info.cpp, plus defining the data structure of the Student_info (object) type.

Source Files

main.cpp (Adjust accordingly)

#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <list>
#include "grade.h"
#include "Student_info.h"
#include "extract_fails.h"

using std::cin;
using std::cout;
using std::endl;
using std::domain_error;
using std::max;
using std::setprecision;
using std::sort;
using std::streamsize;
using std::string;
using std::vector;
using std::list;

// There are two main programs here.
//   - The top one is used to test out extract_fails version 1, 2, and 3.
//   - The bottom one is for testing out extract_fails version 4.
// Simply replace the no1_main() or no2_main() with main() to activate the main program to run

// for testing extract_fails_v1 / v2 / v3 - that uses vector
int no1_main()
{
    vector<Student_info> students;
    Student_info record;

    // read and store all the student's data.
    while (read(cin, record))
        students.push_back(record);

    // Extract the failed students (use extract_fails_v1 / v2 / v3)
    vector<Student_info> students_failed = extract_fails_v3(students);

    // sort vectors
    sort(students.begin(),students.end(),compare);
    sort(students_failed.begin(),students_failed.end(),compare);

    // Report passing students
    cout << "These students have passed." << endl;
    for (vector<Student_info>::const_iterator i = students.begin();
         i != students.end(); ++i)
        cout << i->name << endl;

    // Report failing students
    cout << "These students have failed." << endl;
    for (vector<Student_info>::const_iterator i = students_failed.begin();
         i != students_failed.end(); ++i)
        cout << i->name << endl;

    return 0;
}


// for testing extract_fails_v4 - that uses list
int no2_main()
{
    list<Student_info> students;
    Student_info record;

    // read and store all the student's data.
    while (read(cin, record))
        students.push_back(record);

    // Extract the failed students
    list<Student_info> students_failed = extract_fails_v4(students);

    // sort lists
    students.sort(compare);
    students_failed.sort(compare);

    // Report passing students
    cout << "These students have passed." << endl;
    for (list<Student_info>::const_iterator i = students.begin();
         i != students.end(); ++i)
        cout << i->name << endl;

    // Report failing students
    cout << "These students have failed." << endl;
    for (list<Student_info>::const_iterator i = students_failed.begin();
         i != students_failed.end(); ++i)
        cout << i->name << endl;

    return 0;
}

extract_fails.cpp

#include "Student_info.h"
#include "grade.h"
#include <vector>
#include <list>

using std::vector;
using std::list;


// predicate to determine whether a student failed
// (S5.1/75)
bool fgrade(const Student_info& s)
{
    return grade(s) < 60;
}

// separate passing and failing student records
// version 1: maintain two vectors (pass and fail) within local scope
// Can use up lots of memory resource.
// (S5.1/76)
vector<Student_info> extract_fails_v1(vector<Student_info>& students)
{
    vector<Student_info> pass, fail;

    for (vector<Student_info>::size_type i = 0;
         i != students.size(); ++i)
         if (fgrade(students[i]))
            fail.push_back(students[i]);
         else
            pass.push_back(students[i]);

    students = pass;
    return fail;
}

// separate passing and failing student records
// version 2: maintain one vector (fail) within local scope. Update pass vector by reference.
// correct but potentially slow. (Still use up lots of memory resource but less than version 1)
// (S5.1.1/77)
vector<Student_info> extract_fails_v2(vector<Student_info>& students)
{
    vector<Student_info> fail;
    vector<Student_info>::size_type i = 0;

    // invariant: elements [0,i) of students represent passing grades
    while (i != students.size())
    {
        if (fgrade(students[i]))
        {
            fail.push_back(students[i]);
            students.erase(students.begin() + i);
        }
        else
            ++ i;
    }
    return fail;
}

// separate passing and failing student records
// version 3: iterators but no indexing; still potentially slow
// (S5.3/82)
vector<Student_info> extract_fails_v3(vector<Student_info>& students)
{
    vector<Student_info> fail;
    vector<Student_info>::iterator iter = students.begin();
    while (iter != students.end())
    {
        if (fgrade(*iter))
        {
            fail.push_back(*iter);
            iter = students.erase(iter);
        }
        else
            ++iter;
    }
    return fail;
}

// separate passing and failing student records
// version 4: use list instead of vector (essentially the same as version 3 otherwise)
// (S5.5/85)
list<Student_info> extract_fails_v4(list<Student_info>& students)
{
    list<Student_info> fail;
    list<Student_info>::iterator iter = students.begin();
    while (iter != students.end())
    {
        if (fgrade(*iter))
        {
            fail.push_back(*iter);
            iter = students.erase(iter);
        }
        else
            ++iter;
    }
    return fail;
}

grade.cpp

#include <stdexcept>
#include <vector>
#include "grade.h"
#include "median.h"
#include "Student_info.h"

using std::domain_error;
using std::vector;

// definitions for the grade functions from S4.1/52, S4.1.2/54, S4.2.2/63

// compute a student's overall grade from midterm and final exam
// grades and homework grade (S4.1/52)
double grade(double midterm, double final, double homework)
{
    return 0.2 * midterm + 0.4 * final + 0.4 * homework;
}

// compute a student's overall grade from midterm and final exam grades
// and vector of homework grades.
// this function does not copy its argument, because median (function) does it for us.
// (S4.1.2/54)
double grade(double midterm, double final, const vector<double>& hw)
{
    if (hw.size() == 0)
        throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
}

// this function computes the final grade for a Student_info object
// (S4.2.2/63)
double grade(const Student_info& s)
{
    return grade(s.midterm, s.final, s.homework);
}

median.cpp

// source file for the median function
#include <algorithm>
#include <stdexcept>
#include <vector>

using std::domain_error;
using std::sort;
using std::vector;

// compute the median of a vector<double>
double median(vector<double> vec)
{
    typedef vector<double>::size_type vec_sz;

    vec_sz size = vec.size();
    if (size == 0)
        throw domain_error("median of an empty vector");

    sort(vec.begin(),vec.end());

    vec_sz mid = size/2;

    return size % 2 == 0 ? (vec[mid] + vec[mid-1]) / 2 : vec[mid];
}

Student_info.cpp

#include "Student_info.h"

using std::istream;
using std::vector;

// we are interested in sorting the Student_info object by the student's name
bool compare(const Student_info& x, const Student_info& y)
{
    return x.name < y.name;
}

// read student's name, midterm exam grade, final exam grade, and homework grades
// and store into the Student_info object
// (as defined in S4.2.2/62)
istream& read(istream& is, Student_info& s)
{
    // read and store the student's name and midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // read and store all the student's homework grades
    read_hw(is, s.homework);
    return is;
}

// read homework grades from an input stream into a vector<double>
// (as defined in S4.1.3/57)
istream& read_hw(istream& in, vector<double>& hw)
{
    if (in)
    {
        // get rid of previous contents
        hw.clear();

        // read homework grades
        double x;
        while (in >> x)
            hw.push_back(x);

        // clear the stream so that input will work for the next student
        in.clear();
    }
    return in;
}

Header Files

extract_fails.h

#ifndef GUARD_EXTRACT_FAILS_H
#define GUARD_EXTRACT_FAILS_H

// extract_fails.h
#include <vector>
#include <list>
#include "Student_info.h"

bool fgrade(const Student_info&);
std::vector<Student_info> extract_fails_v1(std::vector<Student_info>&);
std::vector<Student_info> extract_fails_v2(std::vector<Student_info>&);
std::vector<Student_info> extract_fails_v3(std::vector<Student_info>&);
std::list<Student_info> extract_fails_v4(std::list<Student_info>&);

#endif // GUARD_EXTRACT_FAILS_H

grade.h

#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

//grade.h
#include <vector>
#include "Student_info.h"

double grade(double, double, double);
double grade(double, double, const std::vector<double>&);
double grade(const Student_info&);

#endif // GUARD_GRADE_H

median.h

#ifndef GUARD_MEDIAN_H
#define GUARD_MEDIAN_H

// median.h - final version
#include <vector>
double median(std::vector<double>);

#endif // GUARD_MEDIAN_H

Student_info.h

#ifndef GUARD_STUDENT_INFO_H
#define GUARD_STUDENT_INFO_H

// Student_info.h
#include <iostream>
#include <string>
#include <vector>

struct Student_info
{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
};

bool compare(const Student_info&, const Student_info&);
std::istream& read(std::istream&, Student_info&);
std::istream& read_hw(std::istream&, std::vector<double>&);

#endif // GUARD_STUDENT_INFO_H

Test Program

We need to do four tests to test out the extract_fails function version 1, 2, 3, and 4 accordingly.

As seen in the main.cpp, I have made a note on how to do this:

// There are two main programs here.
//   - The top one is used to test out extract_fails version 1, 2, and 3.
//   - The bottom one is for testing out extract_fails version 4.
// Simply replace the no1_main() or no2_main() with main() to activate the main program to run

For clarity purpose I will list out exactly how to change the main.cpp accordingly for this program testing.

To test extract_fails version 1:

  1. Inside no1_main(), replace the extract_fails_XX to extract_fails_v1.
  2. Replace no1_main() with main().

To test extract_fails version 2:

  1. Inside no1_main(), replace the extract_fails_XX to extract_fails_v2.
  2. Replace no1_main() with main().

To test extract_fails version 3:

  1. Inside no1_main(), replace the extract_fails_XX to extract_fails_v3.
  2. Replace no1_main() with main().

To test extract_fails version 4:

  1. Inside no2_main(), replace the extract_fails_XX to extract_fails_v4.
  2. Replace no2_main() with main().

Notes

Note again, no1_main() focuses on vector<string>, whereas no2_main() focuses on list<string>.

I have also taken the liberty to test out the sort function (highlighted in S5.1.1/86 of the text book). i.e.

To sort a vector, do:

    // sort vectors
    sort(students.begin(),students.end(),compare);
    sort(students_failed.begin(),students_failed.end(),compare);

To sort a list, do:

    // sort lists
    students.sort(compare);
    students_failed.sort(compare);

Results

I have run through all 4 versions above and confirm that all give the same result. Some snapshots captured below.

Recall that the finalGrade = ( 20% x midTermGrade ) + (40% x finalTermGrade) + (40% x medianOfHomeworkGrades)

To simplify the test, I know that, if I provide the same value for midTermGrade, finalTermGrade, and homeworkGrades, then the finalGrade should technically be the same value. e.g. if all grades are 90, then the finalGrade is bound to be 90 also. The idea is that, I know the passing grade is 60 or above (and likewise, failing grade is less than 60). By providing the grades for a list of students, I can easily determine (using my head) whether the student should pass or fail. I can then compare with the test result computed by the program. If the program is working as expected, both set of test results should match. (And they indeed do).

Test 1

Aim of this test is to prove that the program is able to split the group of students into two groups – the passing and failing groups.

aaa 90 90 90 90 90
bbb 80 80 80 80 80
ccc 70 70 70 70 70
ddd 60 60 60 60 60
eee 50 50 50 50 50
fff 40 40 40 40 40
ggg 30 30 30 30 30
hhh 10 10 10 10 10
iii 0 0 0 0 0
^Z
^Z
These students have passed.
aaa
bbb
ccc
ddd
These students have failed.
eee
fff
ggg
hhh
iii

Test 2

Aim of this test is essentially the same as test 1, plus the ability to sort.

fff 40 40 40 40 40
bbb 80 80 80 80 80
ccc 70 70 70 70 70
iii 0 0 0 0 0
eee 50 50 50 50 50
aaa 90 90 90 90 90
hhh 10 10 10 10 10
ddd 60 60 60 60 60
ggg 30 30 30 30 30
^Z
^Z
These students have passed.
aaa
bbb
ccc
ddd
These students have failed.
eee
fff
ggg
hhh
iii

Test 3

Aim of this test is to show that the program runs okay when all students have passing grades.

aaa 90 90 90 90 90
bbb 80 80 80 80 80
ccc 70 70 70 70 70
ddd 60 60 60 60 60
^Z
^Z
These students have passed.
aaa
bbb
ccc
ddd
These students have failed.

Test 4

Aim of this test is to show that the program runs okay when all students have failing grades.

eee 50 50 50 50 50
fff 40 40 40 40 40
ggg 30 30 30 30 30
hhh 10 10 10 10 10
iii 0 0 0 0 0
^Z
^Z
These students have passed.
These students have failed.
eee
fff
ggg
hhh
iii

Test 5

Aim of this test is to show that the program runs okay even when we provide no input data at all.

^Z
These students have passed.
These students have failed.

Conclusion

  1. We have successfully put all the files and sub-programs together, implemented the algorithm in an IDE like Code::Block, in a “partitioned” manner (as suggested by the Author at S4.3/65).
  2. We have tested all 4 versions of the extract_fail function. Though I have not proved it here, we know that (according to the Author) that version 1 is least efficient and version 4 is most efficient (2 and 3 are somewhat in the middle).
  3. We have learnt abut iterator, vector and list. Our confident level with using C++, have gone up another notch.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-8

Exercise 4-8

If the following code is legal, what can we infer about the return type of f?

double d = f() [n]

Solution

The return type of the function f is of type double. The logic is a follows:

  • The statement is trying to define a variable double d. i.e. a variable called d of type double.
  • The equal (=) sign assigns whatever that is on the right (of the equal sign), to the left (of the equal sign).
  • As we know that the left-hand-side is of type double, it must be true that the right must also be of type double.
  • Hence, the return type of the function f, is indeed of type double.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-7

Exercise 4-7

Write a program to calculate the average of the numbers stored in a vector<double>.

Solution

In comparison to the previous exercises in this chapter 4, this problem is (fortunately)  quite simple to solve.

Solution Strategy:

  1. In the main program, have a while loop to read individual value x (of type double) one by one, and parse to a container called numbers (of type vector<double>).
  2. Create a function compute_average that reads the vector<double> numbers, and return the average value (of type double).
  3. This function will throw a domain_error if the vector is empty (i.e. no numbers provided). This is to avoid the disgraceful error as a result of division by zero. (note that the average is computed as sum of the values inside the vector, divided by the size of the vector. If the size of the vector is zero, we will have the division by zero issue).
  4. Within the main program, display the average value using the std::cout.

The Program

Since we are only dealing with one function, it may be simpler to just define the function at the top of the main.cpp file (rather than partitioning the programing into multiple C++ header and source files).

The program looks like this.

#include <iostream>   // cout, cin, endl
#include <vector>     // vector
#include <stdexcept>  // domain_error

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::domain_error;


double compute_average(const vector<double>& v)
{
    typedef vector<double>::size_type vtype;
    vtype v_size = v.size();

    if (v_size == 0) throw domain_error("no numbers provided - vector is empty!");

    double sum = 0;
    for (vtype i = 0; i != v_size ; ++i)
    {
        sum += v[i];
    }
    return sum / v_size;
}

int main()
{
    double x;
    vector<double> numbers;
    while (cin >> x)
    {
        numbers.push_back(x);
    }
    try
    {
        cout << "Average: " << compute_average(numbers);
    }
    catch (domain_error e)
    {
        cout << e.what();
    }
    cout << endl;

    return 0;
}

Result

Test 1

The average value of the 6 numbers is computed correctly.

2.5 3.5 4.5 5.5 8.5 9.5
^Z
Average: 5.66667

Test 2

The average value of the 5 numbers is computed correctly.

111.11
222.22
333.33
444.44
555.55
^Z
Average: 333.33

Test 3

For empty vector (no number provided scenario), the try and catch block successfully throw the domain_error message,

^Z
no numbers provided - vector is empty!

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-6

Exercise 4-6

Rewrite the Student_info structure, and the read and grade functions, so that they calculate each student’s grades as part of reading the input, and store only the final grade.

Solution

Having spent a couple of hours solving this problem, it reminds me of the typical experience at work. i.e. changing / updating some else’s code! I got there in the end :)

In this post I will:

  1. Describe the problem and solution in depth.
  2. Summarise my solution to this exercise – under the Project section.
  3. Run some tests using both the old and new solutions / programs – to confirm that we indeed get the same results.

Problem and Solution

The exercise requires us to re-write the chapter 4 partitioned textbook program (as demonstrated in my Solution to Exercise 4-0) in a way that will effectively change the structure of the Student_info object. Note also the new requirement to “calculate each student’s grades as part of reading the input, and store only the final grade” – i.e. we only store the final grade. We do not store the mid-term score, final-term score, and the homework scores in the Student_info objects. We can visualise this change in the Student_info.h header file.

Update the Student_info structure in Student_info.h

Before the change:

struct Student_info
{
    std::string name;
    double midterm;
    double final;
    std::vector<double> homework;
};

After the change:

struct Student_info
{
    std::string name;
    double final_grade;
};

In both cases we keep the string name as it is. The major differences are:

  • During the “before” stage we store all input data (i.e. double midterm, double final, vector<double> homework) to the Student_info object. The program uses a downstream process to compute and display the double final_grade on the fly using a for loop. The double final_grade never get stored in the Student_info object.
  • During “after” stage we read in all input data on the fly, and immediate use these to compute the double final_grade. We store the double final_grade to the Student_info object. We discard the input data (mid-term score, final-term score, and the homework scores).

The change of the Student_info object gives us an hint that, all functions that take the Student_info object as input, might need changing as well. If we following the main program, we bump into the:

while (read(cin, record)) { }

This corresponds to the function istream& read(istream&, Student_info&) in Student_info.cpp source file.

Update a read function in Student_info.cpp

The original function parse all the incoming data to the Student_info object.

Before the change:

istream& read(istream& is, Student_info& s)
{
    // read and store the student's name and midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // read and store all the student's homework grades
    read_hw(is, s.homework);
    return is;
}

In the new version, we only wish to use the double midterm, double final, vector<double> homework temporarily purely for the purpose of computing the double final_grade. To do this, we can create local variables to cater for the temporarily usages, and only store the double final_grade in the end.

After the change:

istream& read(istream& is, Student_info& s)
{
    double midterm;
    double final;
    vector<double> homework;

    is >> s.name >> midterm >> final;

    // read and store all the student's homework scores (to the temporary vector<double> container)
    read_hw(is, homework);

    // compute the student's overall score, and store to the Student_info object
    try
    {
        s.final_grade = grade(midterm, final, homework);
    }
    catch (domain_error e)
    {
        s.final_grade = -1;  // indicating student has done no homework
    }
    return is;
}

See that the new version function istream& read(istream&, Student_info&), parses the mid-term score to the local variable double midterm, the final-term score to the local variable double final, the homework scores to the local container vector<double> homework (using the read_hw function, which is unchanged).

I now have a try block to compute the double final_grade straight away (and store in the Student_info object) using the function grade(double, double, double), which is unchanged. If no error, the final_grade is stored in the Student_info object. Otherwise (when no homework is submitted. i.e. the homework vector is empty), then I force set the final_grade to -1 to imply no homework. As the score is always positive, we can be very certain that -1 is not the result of computation of the final_grade.

In summary, the enhanced read function enables us to “fill in” the values of the new Student_info structure – for each Student_info object, we require the student’s name, and the student’s (computed) final grade.

Update the main program to display report

The original main program contains a step to compute the double final_grade on the fly and display within a for loop. In the new version program we no longer need to compute double final_grade on the fly, as the read function above has taken care this and store the double final_grade away as a property of the Student_info object. In the new version main program, we just need to simply use the std::cout to display Student_info::final_grade.

Before the change

    // write the names and grades
    for (vector<Student_info>::size_type i = 0;
         i != students.size(); ++i)
    {
        //write the name, padded on teh right to maxlen + 1 characters
        cout << students[i].name
             << string(maxlen + 1 - students[i].name.size(), ' ');

         //compute and write the grade
        try
        {
            double final_grade = grade(students[i]);
            streamsize prec = cout.precision();
            cout << setprecision(3) << final_grade
                 << setprecision(prec);
        }
        catch (domain_error e)
        {
            cout << e.what();
        }
        cout << endl;
    }

After the change

    // write the names and grades
    for (vector<Student_info>::size_type i = 0;
         i != students.size(); ++i)
    {
        // write the name, padded on the right to maxlen + 1 characters
        cout << students[i].name
             << string(maxlen + 1 - students[i].name.size(), ' ');

        streamsize prec = cout.precision();

        // write the overall_grade out directly
        cout << setprecision(3)
             << students[i].final_grade
             << setprecision(prec) << endl;
    }

Note that the new version step purely display the results. Much simplier than the original version.

What we should also notice is that the new verrsion no longer require the use of the function double grade(const Student_info&) – we can safely remove this from both the grade.cpp source file and grade.h header file.

The Project – Putting Everything Together

I now wrap up the solution to this exercise by documenting the entire project – which is essentially and updated version of my Solution to Exercise 4-0. I have tried my best to highlight the “before” and “after” via commenting within the codes. I will run some tests at the end of the post using both the old and new solutions / programs – to confirm that we do indeed get the same results.

C++ Source Files

  • main.cpp – this is the first program that is run during the implementation phase.
  • grade.cpp – contains all functions relating to computing grades.
  • median.cpp – contains all functions relating to computing median.
  • Student_info.cpp – contains all functions relating to handling a Student_info object.

C++ Header Files

  • grade.h – declare the functions as defined in grade.cpp
  • median.h – declare the functions as defined in median.cpp
  • Student_info.h – declare the functions as defined in Student_info.cpp, plus defining the data structure of the Student_info (object) type.

This diagram below shows what the Code::Block Management Tree look like after successful creation of these files (note that this is the same as the one in the Solution to Exercise 4-0).

Acpp4p0MgntTree

Source Files

main.cpp

#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
/* #include <stdexcept> */  // Exercise 4-6: no longer required
#include <string>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::cin;
using std::cout;
using std::endl;
/* using std::domain_error; */   // Exercise 4-6: no longer required
using std::max;
using std::setprecision;
using std::sort;
using std::streamsize;
using std::string;
using std::vector;

// Exercise 4-6: adjusted from the for loop onwards
int main()
{
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;   // the length of the longest name

    // read and store all the student's data.
    // Invariant:   students contain all the student records read so far
    //              maxlen contains the length of the longest name in students
    while (read(cin, record))
    {
        // find the length of longest name
        maxlen = max(maxlen, record.name.size());
        students.push_back(record);
    }

    // alphabetize the student records
    sort(students.begin(), students.end(), compare);

    // write the names and grades
    for (vector<Student_info>::size_type i = 0;
         i != students.size(); ++i)
    {
        // write the name, padded on the right to maxlen + 1 characters
        cout << students[i].name
             << string(maxlen + 1 - students[i].name.size(), ' ');

        streamsize prec = cout.precision();

        // write the overall_grade out directly
        cout << setprecision(3)
             << students[i].final_grade
             << setprecision(prec) << endl;
    }

    cout << endl;
    cout << "*****************************************************************" << endl;
    cout << "*** Note: a grade of -1 implies student has done no homework. ***" << endl;
    cout << "*****************************************************************" << endl;
    return 0;
}

grade.cpp

#include <stdexcept>  // Exercise 4-6:  added this
#include <vector>
#include "grade.h"    // Exercise 4-6:  added this
#include "median.h"
#include "Student_info.h"

using std::domain_error;
using std::vector;

// definitions for the grade functions from S4.1/52, S4.1.2/54, S4.2.2/63

// compute a student's overall grade from midterm and final exam
// grades and homework grade (S4.1/52)
double grade(double midterm, double final, double homework)
{
    return 0.2 * midterm + 0.4 * final + 0.4 * homework;
}

// compute a student's overall grade from midterm and final exam grades
// and vector of homework grades.
// this function does not copy its argument, because median (function) does it for us.
// (S4.1.2/54)
double grade(double midterm, double final, const vector<double>& hw)
{
    if (hw.size() == 0)
        throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
}

// Exercise 4-6: the following function is no longer required.
/*
double grade(const Student_info& s)
{
    return grade(s.midterm, s.final, s.homework);
}
*/

median.cpp

// source file for the median function
#include <algorithm>
#include <stdexcept>
#include <vector>

using std::domain_error;
using std::sort;
using std::vector;

// compute the median of a vector<double>
double median(vector<double> vec)
{
    typedef vector<double>::size_type vec_sz;

    vec_sz size = vec.size();
    if (size == 0)
        throw domain_error("median of an empty vector");

    sort(vec.begin(),vec.end());

    vec_sz mid = size/2;

    return size % 2 == 0 ? (vec[mid] + vec[mid-1]) / 2 : vec[mid];
}

Student_info.cpp

#include "Student_info.h"
#include "grade.h"
#include <stdexcept>
/* #include <iostream> */   // Exercise 4-6: no longer required

using std::istream;
using std::vector;
using std::domain_error;
/* using std::cout; */    // Exercise 4-6: no longer required

// we are interested in sorting the Student_info object by the student's name
bool compare(const Student_info& x, const Student_info& y)
{
    return x.name < y.name;
}

// Exercise 4-6:  modified the following function
//    now read student's name, and compute the overall_grade directly.
//    Store only the student's name and final_grade to the Student_info object
//    we use local variable to handle the rest

istream& read(istream& is, Student_info& s)
{
    double midterm;
    double final;
    vector<double> homework;

    is >> s.name >> midterm >> final;

    // read and store all the student's homework scores (to the temporary vector<double> container)
    read_hw(is, homework);

    // compute the student's overall score, and store to the Student_info object
    try
    {
        s.final_grade = grade(midterm, final, homework);
    }
    catch (domain_error e)
    {
        s.final_grade = -1;  // indicating student has done no homework
    }
    return is;
}

// read homework grades from an input stream into a vector<double>
// (as defined in S4.1.3/57)
istream& read_hw(istream& in, vector<double>& hw)
{
    if (in)
    {
        // get rid of previous contents
        hw.clear();

        // read homework grades
        double x;
        while (in >> x)
            hw.push_back(x);

        // clear the stream so that input will work for the next student
        in.clear();
    }
    return in;
}

Header Files

grade.h

#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

//grade.h
#include <vector>
#include "Student_info.h"

double grade(double, double, double);
double grade(double, double, const std::vector<double>&);

// Exercise 4-6: the following function is no longer required.
/*
 double grade(const Student_info&);
*/

#endif // GUARD_GRADE_H

median.h

#ifndef GUARD_MEDIAN_H
#define GUARD_MEDIAN_H

// median.h - final version
#include <vector>
double median(std::vector<double>);

#endif // GUARD_MEDIAN_H

Student_info.h

#ifndef GUARD_STUDENT_INFO_H
#define GUARD_STUDENT_INFO_H

// Student_info.h
#include <iostream>
#include <string>
#include <vector>

struct Student_info
{
    std::string name;

    // Exercise 4-6: no longer need to the followings in the Student_info object
    /*
    double midterm, final;
    std::vector<double> homework;
    */

    // Exercise 4-6: store only final_grade
    double final_grade;
};

bool compare(const Student_info&, const Student_info&);
std::istream& read(std::istream&, Student_info&);
std::istream& read_hw(std::istream&, std::vector<double>&);

#endif // GUARD_STUDENT_INFO_H

Test Program

I will now run some tests using both the original and the revised programs. If the revised program report the same figures as the original program, we should be fairly confident that our revised program is (more or less) correct.

  • Test 1 – submit all required inputs. i.e. Student’s name, mid-term score, final-term score, and homework scores.
  • Test 2 – same as Test 1 except this time, we ommit the homework scores.

Test 1 Result

Original Program (Solution to Exercise 4-0)

Johnny 10 20 30 40 50
Fred 20 30 40 50 60
Joe 30 40 50 60 70
^Z
^Z
Fred   36
Joe    46
Johnny 26

Revised Program (Solution to Exercise 4-6)

Johnny 10 20 30 40 50
Fred 20 30 40 50 60
Joe 30 40 50 60 70
^Z
^Z
Fred   36
Joe    46
Johnny 26

*****************************************************************
*** Note: a grade of -1 implies student has done no homework. ***
*****************************************************************

Both programs gave the same results – as expected.

Test 2 Result

Original Program (Solution to Exercise 4-0)

Johnny 10 20
Fred 20 30
Joe 30 40 50 60 70
^Z
^Z
Fred   student has done no homework
Joe    46
Johnny student has done no homework

Revised Program (Solution to Exercise 4-6)

Johnny 10 20
Fred 20 30
Joe 30 40 50 60 70
^Z
^Z
Fred   -1
Joe    46
Johnny -1

*****************************************************************
*** Note: a grade of -1 implies student has done no homework. ***
*****************************************************************

Both programs gave the same results – as expected.

Conclusion

We have rewritten the program (from Solution to Exercise 4-0) as required by the problem. The test results suggest the revised program is reasonably correct.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-5

Exercise 4-5

Write a function that reads words from an input stream and stores them in a vector. Use that function both to write programs that count the number of words in the input, and to count how many times each word occurred.

Solution

This is a somewhat simplified version of Solution to Exercise 4-0, in the sense that 4-0 processes the std::vector<Student_Info> container, whereas 4-5 deals processes the std::vector<std::string> container.

Solution Strategy

  1. Create a readWords function to parse individual std::string objects (i.e. words) to a std::vector<std::string> container wordList. We can reuse the skeleton logic from the std::istream& read_hw function defined in the Solution to Exercise 4-0.
  2. The word count is essentially the number of elements within the wordList container. i.e. wordList.size(). Though not strictly neccessary, I have created a function called countWords which does the same thing.
  3. Create a countUniqueWords function to calculate the number of unique words within the container. We can reuse the skeleton logic from the Solution to Exercise 3-3.
  4. Invoke these functions via the main program.

The Project

As usual, I partition the program into smaller chunks – i.e. in C++ source files and header files.

C++ Source Files

  • main.cpp – this is the first program that is run during the implementation phase.
  • readWords.cpp – contains all functions relating to creating and processing the std::vector<std::string> container.

C++ Header Files

  • readWords.h– declare the functions as defined in readWords.cpp.

This diagram below shows what the Code::Block Management Tree look like after successful creation of these files.

Acpp4p5MgntTree

Source Files

main.cpp

#include <iostream>
#include "readWords.h"

using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;

int main()
{
    vector<string> wordList;
    readWords(cin, wordList);
    displayWords(wordList);
    cout << "No. Words = " << countWords(wordList) << endl;
    cout << "No. Unique Words = " << countUniqueWords(wordList) << endl;
    return 0;
}

readWords.cpp

#include "readWords.h"
#include <algorithm>

using std::vector;
using std::string;
using std::istream;
using std::cout;
using std::endl;
using std::sort;

// read string objects one-by-one, and append to a vector<string> container
// note: I reuse the Student_info.cpp from the Solution to Exercise 4-0
istream& readWords(istream& in, vector<string>& words)
{
    if (in)
    {
        // get rid of previous contents
        words.clear();

        // read input string one by one, and append to vector<string> container
        string x;
        while (in >> x)
            words.push_back(x);

        // clear the stream error status (if any) so that input will work for the next set of inputs
        in.clear();
    }
    return in;
}

// function to display the elements within the vector<string> container
int displayWords(const vector<string>& words)
{
    for (vector<string>::size_type i = 0; i != words.size(); ++i)
        cout << words[i] << endl;
    return 0;
}

// function to count elements within a vector<string> container
int countWords(const vector<string>& words)
{
    return words.size();
}

// function to count unique elements within a vector<string> container
// note: borrowed from the solution to exercise 3-3
int countUniqueWords(const vector<string>& words)
{
    vector<string> tmpWords = words;

    typedef vector<string>::size_type vec_sz;
    const vec_sz numElements = tmpWords.size();

    if (numElements == 0) return 0;
    else if (numElements == 1) return 1;

    sort(tmpWords.begin(),tmpWords.end());
    const vec_sz numLoops = numElements - 1;

    vec_sz A = 0;
    vec_sz B = 1;
    int numUniqueWords = 1;
    for (vec_sz i = 0; i != numLoops; ++i)
    {
        if (tmpWords[B] != tmpWords[A])
            ++numUniqueWords;
        ++A;
        ++B;
    }
    return numUniqueWords;
}

Header Files

readWords.h

#ifndef GUARD_READWORDS_H
#define GUARD_READWORDS_H

#include <iostream>
#include <vector>
#include <string>

std::istream& readWords(std::istream&, std::vector<std::string>&);
int displayWords(const std::vector<std::string>&);
int countWords(const std::vector<std::string>&);
int countUniqueWords(const std::vector<std::string>&);

#endif // GUARD_READWORDS

Result

Test 1 – Supply 0 Words

^Z
No. Words = 0
No. Unique Words = 0

Test 2 – Supply 1 Word

aaa
^Z
aaa
No. Words = 1
No. Unique Words = 1

Test 3 – Supply Multiple Words

aaa bbb ccc ddd aaa bbb aaa
^Z
aaa
bbb
ccc
ddd
aaa
bbb
aaa
No. Words = 7
No. Unique Words = 4

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-4

Exercise 4-4

Now change your squares program to use double values instead of ints. Use manipulators to manage the output so that the values line up in columns.

Solution

As the nature of this question is somewhat similar to Exercise 4-3, it makes sense to re-use (some aspect of) Solution to Exercise 4-3, such as the program skeleton structure.

This time however, the question asks us to use double values instead of ints. In other words, the final output will contain two columns. Column 1 lists a defined set of double values. Column 2 lists the square of column 1. We can write the program in a way that the two columns have suitable (fixed) widths and precision values (number of significant). So the output will consistently look “neat”.

In this post I will describe the C++ program that I write to solve this particular problem. I would like to emphasise that the program has its limitation. It works pretty well EXCEPT the range of between -1 and +1. More work may be required to make the program cope with this “taboo” range. For now, and for simplicity sake, I will assume the program is “good enough” as it covers pretty most types of scenarios – the program works range for most negative and positive ranges. (We will avoid this taboo range for now).

Algorithm

Before writing the program it is very important to have a mental image of what the width and precision mean / look like, which will enable us to form the underlying algorithm.

Algorithm – Compute Width

Width is the total number of characters required to fit the output “number-string” to the output console window. The width needs to be “big enough” to fit the number string – otherwise the right part of the string is trimmed away.

We compute the total width as the sum of the followings:

  • Negative sign (for negative number only): takes up 1 character width.
  • Leading space: takes up 1 character width.
  • Integer width: can be computed by couting number of “division by 10” required to make the number less than 1.
  • Decimal point: takes up 1 character width.
  • Decimals: we define how many number of decimals we wish to display.

Examples:

  • e.g. if the number is 123.45 (and we set the Decimals to 2), then total width required is 7 (1 leading space + 3 integer width + 1 decimal point + 2 decimals).
  • e.g. if the number is -123.45 (and we set the Decimals to 2), then total width required is 8 (1 negative sign + 1 leading space + 3 integer width + 1 decimal point + 2 decimals).

Algorithm – Compute Precision

Precision is the total number of significant and is used to control how we display the number, keeping the width constant.

Precision = Width – Gap

  • If negative number: Gap = 3 (1 negative sign + 1 leading space + 1 decimal point)
  • If positive number: Gap = 2 (1 leading space + 1 decimal point)

Using the same examples

  • if the number is 123.45 and we wish to display the whole 123.45 string, we need a precision of 5 (7 Width – 2 Gap).
  • if the number is -123.45 and we wish to display the whole 1-23.45 string, we need a precision of 5 (8 Width – 3 Gap).

Being able to visualise Width and Precision, we are now in a position to form our solution strategy.

Solution Strategy

  1. We re-use the skeleton program structure as per Solution to Exercise 4-3 by (1) amending the main program, (2) overloading the getStreamWidth function, and (3) updating the required header files.
  2. The output will consist of column 1 and column 2. Column 1 is the number (for squaring). Column 2 is the square of column 1.
  3. We define important properties: the asymmetric range [const double m, const double n), and the interval const double i.
  4. We also define the core format of the columns by specifying the max number of decimals const streamsize d. This will aid the width computation of each column.
  5. We create a new (overloaded) getStreamWidth function to return a total width (of type std::streamsize)  required to fit a number. For learning sake, I have decided to overload the getStreamWidth function (as per previous exercise). The function will require two input parameters (1) double number, and (2) std::streamsize numDecimals. The double number will tell us whether we require an extra space for the negative sign. It also helps us to compute the integer width (by counting number of “division by 10” required to make the number less than 1). The std::streamsize numDecimals provides the remaining information for us to compute the total width. See the Algorithm – Compute Width section above to see the algorithm used.
  6. We identify the max width required for column 1 and column 2. For negative range, the start number will likely have more digits (and bigger width). For positive range, the end number is likely to have more digits (and bigger width). To find the max width, we therefore compute the width for both start and end number, and pick out the bigger width with the std::max function.
  7. We dynamically compute the precision required for column 1 and column 2, using the algorithm described in the Algorithm – Compute Precision section above.
  8. We use a while loop to create column 1, dynamically create column 2, and apply the pre-computed widths and precisions against the columns – so the output displays as expected.
  9. (Optional) We end the output step by displaying some core parameters used, such as the widths and precisions computed for column 1 and 2. This will aid us understand the program better. It’s purely for educational purposes.

The Project

Like my Solution to Exercise 4-3, I have decided to partition the program as follows. I have (1) amended the main program, (2) overloaded the getStreamWidth function, and (3) updated the required header files.

C++ Source Files

  • main.cpp – this is the first program that is run during the implementation phase.
  • getStreamWidth.cpp – contains all functions relating to obtaining the stream widths.

C++ Header Files

This diagram below shows what the Code::Block Management Tree look like after successful creation of these files.

Acpp4p4MgntTree

Source Files

main.cpp

{
    // Adjust these initial values for different tests
    const double m = 5;          // start of range (inclusive)
    const double n = 6;          // end of range (exclusive)
    const double i = 0.005;      // incremental interval
    const streamsize d = 3;      // number of decimal places

    // find the maxwidth for column 1 and 2
    const streamsize col1Width = max(getStreamWidth(m, d), getStreamWidth(n, d));
    const streamsize col2Width = max(getStreamWidth(m * m, d), getStreamWidth(n * n, d));

    // Compute precision.
    // Precision = width - gap
    // If involves negative region: precision = width - (leading space + decimal point + negative sign) = width - 3.
    // else, precision = width - ( leading space + decimal point ) = width - 2.
    streamsize gap;
    if (m < 0 || n < 0)
        gap = 3; // leading space + decimal point + negative sign
    else
        gap = 2; // leading space + decimal point

    const streamsize col1Precision = col1Width - gap;
    const streamsize col2Precision = col2Width - 2;   // the square of a number is always positive. i.e. no negative sign!

    // get ready to print report
    double y = m;
    while (y < n)
    {
        cout << setw(col1Width) << setprecision(col1Precision) << y
             << setw(col2Width) << setprecision(col2Precision) << (y * y)
             << setw(0) << endl;
        y += i;
    }

    // display a summary
    cout << "start = " << m << ", end = " << n << ", increment = " << i << ", decimalPlaces = " << d << endl;
    cout << "Column 1 width = " << col1Width << " | Column 2 width = " << col2Width << endl;
    return 0;
}

getStreamWidth.cpp

#include <ios>

using std::streamsize;

// Original function for Solution to Exercise 4-3
// return the required streamsize to fit a particular integer number
streamsize getStreamWidth(int number)
{

    streamsize numDigits;

    // initialise numDigits and number depending on whether value is positive / negative.
    // If negative, require at least 2 spaces to fit the leading empty space string and the negative sign
    // If positive, require at least 1 space to fit the leading empty space string
    if (number < 0)
    {
        numDigits = 2;
        number *= -1;
    }
    else numDigits = 1;

    // numDigits is the number of divisions required to make number approaches zero (plus leading space and sign)
    // i.e. this is equivalent to the total stream width required
    while (number != 0)
    {
        ++numDigits;
        number /= 10;
    }

    return numDigits;
}

// overloaded function for Solution to Exercise 4-4
// return the required streamsize to fit a particular double number, given number of decimals
streamsize getStreamWidth(double number, streamsize numDecimals)
{
    streamsize numDigits = numDecimals;

    // initialise numDigits and number depending on whether value is positive / negative.
    // If negative, require at least 2 spaces to fit the leading empty space string and the negative sign
    // If positive, require at least 1 space to fit the leading empty space string
    if (number < 0)
    {
        numDigits = 3 + numDecimals;  // (leading space + negative sign + decimal point) + decimals
        number *= -1;
    }
    else numDigits = 2 + numDecimals;  // (leading space + decimal point) + decimals

    // numDigits is the number of divisions required to make number approaches zero (plus leading space and sign)
    // i.e. this is equivalent to the total stream width required
    while (number >= 1)
    {
        ++numDigits;
        number /= 10;
    }
    return numDigits;
}

Header Files

getStreamWidth.h

#ifndef GUARD_GETSTREAMWIDTH_H
#define GUARD_GETSTREAMWIDTH_H

std::streamsize getStreamWidth(int number);
std::streamsize getStreamWidth(double number, std::streamsize numDecimals);

#endif // GUARD_GETSTREAMWIDTH_H

Result

I shall adjust the values [const double m, const double n), increment const double i, and decimals const streamsize d. To give us some assurance that the program is fairly stable and consistent.

Experiment 1 – Define Parameters

    const double m = 5;          // start of range (inclusive)
    const double n = 6;          // end of range (exclusive)
    const double i = 0.005;      // incremental interval
    const streamsize d = 3;      // number of decimal places

Experiment 1 – Result

     5     25
 5.005  25.05
  5.01   25.1
 5.015  25.15
  5.02   25.2
 5.025 25.251
  5.03 25.301
 5.035 25.351
  5.04 25.402
 5.045 25.452
  5.05 25.502
 5.055 25.553
  5.06 25.604
 5.065 25.654
  5.07 25.705
 5.075 25.756
  5.08 25.806
 5.085 25.857
  5.09 25.908
 5.095 25.959
   5.1  26.01
 5.105 26.061
  5.11 26.112
 5.115 26.163
  5.12 26.214
 5.125 26.266
  5.13 26.317
 5.135 26.368
  5.14  26.42
 5.145 26.471
  5.15 26.522
 5.155 26.574
  5.16 26.626
 5.165 26.677
  5.17 26.729
 5.175 26.781
  5.18 26.832
 5.185 26.884
  5.19 26.936
 5.195 26.988
   5.2  27.04
 5.205 27.092
  5.21 27.144
 5.215 27.196
  5.22 27.248
 5.225 27.301
  5.23 27.353
 5.235 27.405
  5.24 27.458
 5.245  27.51
  5.25 27.562
 5.255 27.615
  5.26 27.668
 5.265  27.72
  5.27 27.773
 5.275 27.826
  5.28 27.878
 5.285 27.931
  5.29 27.984
 5.295 28.037
   5.3  28.09
 5.305 28.143
  5.31 28.196
 5.315 28.249
  5.32 28.302
 5.325 28.356
  5.33 28.409
 5.335 28.462
  5.34 28.516
 5.345 28.569
  5.35 28.622
 5.355 28.676
  5.36  28.73
 5.365 28.783
  5.37 28.837
 5.375 28.891
  5.38 28.944
 5.385 28.998
  5.39 29.052
 5.395 29.106
   5.4  29.16
 5.405 29.214
  5.41 29.268
 5.415 29.322
  5.42 29.376
 5.425 29.431
  5.43 29.485
 5.435 29.539
  5.44 29.594
 5.445 29.648
  5.45 29.702
 5.455 29.757
  5.46 29.812
 5.465 29.866
  5.47 29.921
 5.475 29.976
  5.48  30.03
 5.485 30.085
  5.49  30.14
 5.495 30.195
   5.5  30.25
 5.505 30.305
  5.51  30.36
 5.515 30.415
  5.52  30.47
 5.525 30.526
  5.53 30.581
 5.535 30.636
  5.54 30.692
 5.545 30.747
  5.55 30.802
 5.555 30.858
  5.56 30.914
 5.565 30.969
  5.57 31.025
 5.575 31.081
  5.58 31.136
 5.585 31.192
  5.59 31.248
 5.595 31.304
   5.6  31.36
 5.605 31.416
  5.61 31.472
 5.615 31.528
  5.62 31.584
 5.625 31.641
  5.63 31.697
 5.635 31.753
  5.64  31.81
 5.645 31.866
  5.65 31.922
 5.655 31.979
  5.66 32.036
 5.665 32.092
  5.67 32.149
 5.675 32.206
  5.68 32.262
 5.685 32.319
  5.69 32.376
 5.695 32.433
   5.7  32.49
 5.705 32.547
  5.71 32.604
 5.715 32.661
  5.72 32.718
 5.725 32.776
  5.73 32.833
 5.735  32.89
  5.74 32.948
 5.745 33.005
  5.75 33.062
 5.755  33.12
  5.76 33.178
 5.765 33.235
  5.77 33.293
 5.775 33.351
  5.78 33.408
 5.785 33.466
  5.79 33.524
 5.795 33.582
   5.8  33.64
 5.805 33.698
  5.81 33.756
 5.815 33.814
  5.82 33.872
 5.825 33.931
  5.83 33.989
 5.835 34.047
  5.84 34.106
 5.845 34.164
  5.85 34.222
 5.855 34.281
  5.86  34.34
 5.865 34.398
  5.87 34.457
 5.875 34.516
  5.88 34.574
 5.885 34.633
  5.89 34.692
 5.895 34.751
   5.9  34.81
 5.905 34.869
  5.91 34.928
 5.915 34.987
  5.92 35.046
 5.925 35.106
  5.93 35.165
 5.935 35.224
  5.94 35.284
 5.945 35.343
  5.95 35.402
 5.955 35.462
  5.96 35.522
 5.965 35.581
  5.97 35.641
 5.975 35.701
  5.98  35.76
 5.985  35.82
  5.99  35.88
 5.995  35.94
     6     36
start = 5, end = 6, increment = 0.005, decimalPlaces = 3
Column 1 width = 6 | Column 2 width = 7

Process returned 0 (0x0)   execution time : 0.509 s
Press any key to continue.

Experiment 2 – Define Parameters

    const double m = -6;          // start of range (inclusive)
    const double n = -5;          // end of range (exclusive)
    const double i = 0.005;      // incremental interval
    const streamsize d = 3;      // number of decimal places

Experiment 2 – Result

     -6     36
 -5.995  35.94
  -5.99  35.88
 -5.985  35.82
  -5.98  35.76
 -5.975 35.701
  -5.97 35.641
 -5.965 35.581
  -5.96 35.522
 -5.955 35.462
  -5.95 35.403
 -5.945 35.343
  -5.94 35.284
 -5.935 35.224
  -5.93 35.165
 -5.925 35.106
  -5.92 35.046
 -5.915 34.987
  -5.91 34.928
 -5.905 34.869
   -5.9  34.81
 -5.895 34.751
  -5.89 34.692
 -5.885 34.633
  -5.88 34.574
 -5.875 34.516
  -5.87 34.457
 -5.865 34.398
  -5.86  34.34
 -5.855 34.281
  -5.85 34.223
 -5.845 34.164
  -5.84 34.106
 -5.835 34.047
  -5.83 33.989
 -5.825 33.931
  -5.82 33.872
 -5.815 33.814
  -5.81 33.756
 -5.805 33.698
   -5.8  33.64
 -5.795 33.582
  -5.79 33.524
 -5.785 33.466
  -5.78 33.408
 -5.775 33.351
  -5.77 33.293
 -5.765 33.235
  -5.76 33.178
 -5.755  33.12
  -5.75 33.063
 -5.745 33.005
  -5.74 32.948
 -5.735  32.89
  -5.73 32.833
 -5.725 32.776
  -5.72 32.718
 -5.715 32.661
  -5.71 32.604
 -5.705 32.547
   -5.7  32.49
 -5.695 32.433
  -5.69 32.376
 -5.685 32.319
  -5.68 32.262
 -5.675 32.206
  -5.67 32.149
 -5.665 32.092
  -5.66 32.036
 -5.655 31.979
  -5.65 31.923
 -5.645 31.866
  -5.64  31.81
 -5.635 31.753
  -5.63 31.697
 -5.625 31.641
  -5.62 31.584
 -5.615 31.528
  -5.61 31.472
 -5.605 31.416
   -5.6  31.36
 -5.595 31.304
  -5.59 31.248
 -5.585 31.192
  -5.58 31.136
 -5.575 31.081
  -5.57 31.025
 -5.565 30.969
  -5.56 30.914
 -5.555 30.858
  -5.55 30.803
 -5.545 30.747
  -5.54 30.692
 -5.535 30.636
  -5.53 30.581
 -5.525 30.526
  -5.52  30.47
 -5.515 30.415
  -5.51  30.36
 -5.505 30.305
   -5.5  30.25
 -5.495 30.195
  -5.49  30.14
 -5.485 30.085
  -5.48  30.03
 -5.475 29.976
  -5.47 29.921
 -5.465 29.866
  -5.46 29.812
 -5.455 29.757
  -5.45 29.703
 -5.445 29.648
  -5.44 29.594
 -5.435 29.539
  -5.43 29.485
 -5.425 29.431
  -5.42 29.376
 -5.415 29.322
  -5.41 29.268
 -5.405 29.214
   -5.4  29.16
 -5.395 29.106
  -5.39 29.052
 -5.385 28.998
  -5.38 28.944
 -5.375 28.891
  -5.37 28.837
 -5.365 28.783
  -5.36  28.73
 -5.355 28.676
  -5.35 28.623
 -5.345 28.569
  -5.34 28.516
 -5.335 28.462
  -5.33 28.409
 -5.325 28.356
  -5.32 28.302
 -5.315 28.249
  -5.31 28.196
 -5.305 28.143
   -5.3  28.09
 -5.295 28.037
  -5.29 27.984
 -5.285 27.931
  -5.28 27.878
 -5.275 27.826
  -5.27 27.773
 -5.265  27.72
  -5.26 27.668
 -5.255 27.615
  -5.25 27.563
 -5.245  27.51
  -5.24 27.458
 -5.235 27.405
  -5.23 27.353
 -5.225 27.301
  -5.22 27.248
 -5.215 27.196
  -5.21 27.144
 -5.205 27.092
   -5.2  27.04
 -5.195 26.988
  -5.19 26.936
 -5.185 26.884
  -5.18 26.832
 -5.175 26.781
  -5.17 26.729
 -5.165 26.677
  -5.16 26.626
 -5.155 26.574
  -5.15 26.523
 -5.145 26.471
  -5.14  26.42
 -5.135 26.368
  -5.13 26.317
 -5.125 26.266
  -5.12 26.214
 -5.115 26.163
  -5.11 26.112
 -5.105 26.061
   -5.1  26.01
 -5.095 25.959
  -5.09 25.908
 -5.085 25.857
  -5.08 25.806
 -5.075 25.756
  -5.07 25.705
 -5.065 25.654
  -5.06 25.604
 -5.055 25.553
  -5.05 25.503
 -5.045 25.452
  -5.04 25.402
 -5.035 25.351
  -5.03 25.301
 -5.025 25.251
  -5.02   25.2
 -5.015  25.15
  -5.01   25.1
 -5.005  25.05
     -5     25
start = -6, end = -5, increment = 0.005, decimalPlaces = 3
Column 1 width = 7 | Column 2 width = 7

Process returned 0 (0x0)   execution time : 0.364 s
Press any key to continue.

Experiment 3 – Define Parameters

    const double m = 1;          // start of range (inclusive)
    const double n = 1000;          // end of range (exclusive)
    const double i = 0.1;      // incremental interval
    const streamsize d = 2;      // number of decimal places

Experiment 3 – Result

       1          1
     1.1       1.21
     1.2       1.44
     1.3       1.69
     1.4       1.96
     1.5       1.25
     .....etc ....
   990.1  980298.01
   990.2  980496.04
   990.3  980694.09
   990.4  980892.16
   990.5  981090.25
   990.6  981288.36
   990.7  981486.49
   990.8  981684.64
   990.9  981882.81
     991     982081
   991.1  982279.21
   991.2  982477.44
   991.3  982675.69
   991.4  982873.96
   991.5  983072.25
   991.6  983270.56
   991.7  983468.89
   991.8  983667.24
   991.9  983865.61
     992     984064
   992.1  984262.41
   992.2  984460.84
   992.3  984659.29
   992.4  984857.76
   992.5  985056.25
   992.6  985254.76
   992.7  985453.29
   992.8  985651.84
   992.9  985850.41
     993     986049
   993.1  986247.61
   993.2  986446.24
   993.3  986644.89
   993.4  986843.56
   993.5  987042.25
   993.6  987240.96
   993.7  987439.69
   993.8  987638.44
   993.9  987837.21
     994     988036
   994.1  988234.81
   994.2  988433.64
   994.3  988632.49
   994.4  988831.36
   994.5  989030.25
   994.6  989229.16
   994.7  989428.09
   994.8  989627.04
   994.9  989826.01
     995     990025
   995.1  990224.01
   995.2  990423.04
   995.3  990622.09
   995.4  990821.16
   995.5  991020.25
   995.6  991219.36
   995.7  991418.49
   995.8  991617.64
   995.9  991816.81
     996     992016
   996.1  992215.21
   996.2  992414.44
   996.3  992613.69
   996.4  992812.96
   996.5  993012.25
   996.6  993211.56
   996.7  993410.89
   996.8  993610.24
   996.9  993809.61
     997     994009
   997.1  994208.41
   997.2  994407.84
   997.3  994607.29
   997.4  994806.76
   997.5  995006.25
   997.6  995205.76
   997.7  995405.29
   997.8  995604.84
   997.9  995804.41
     998     996004
   998.1  996203.61
   998.2  996403.24
   998.3  996602.89
   998.4  996802.56
   998.5  997002.25
   998.6  997201.96
   998.7  997401.69
   998.8  997601.44
   998.9  997801.21
     999     998001
   999.1  998200.81
   999.2  998400.64
   999.3  998600.49
   999.4  998800.36
   999.5  999000.25
   999.6  999200.16
   999.7  999400.09
   999.8  999600.04
   999.9  999800.01
start = 1, end = 1000, increment = 0.1, decimalPlaces = 2
Column 1 width = 8 | Column 2 width = 11

Process returned 0 (0x0)   execution time : 4.964 s
Press any key to continue.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-3

Exercise 4-3

What happens if we rewrite the previous program to allow values up to but not including 1000 but neglect to change the arguments to setw? Rewrite the program to be more robust in the face of changes that allow i to grow without adjusting the setw arguments.

Solution

Though there are many ways to solve this problem, below is the solution strategy that I use, with the aim of enhanced flexibility:

  1. Obtain the asymmetric range [m,n) from the user with the condition of n > m. In other words, m is the startNumber. n – 1 is the endNumber.
  2. Compute the number of elements within this asymmetric range as n – m. This is equivalent to the number of loops required (or number of rows to output).
  3. Create a function getStreamWidth that will be used to automatically compute the maximum width required for column 1 (the list of numbers), and column 2 (the square of column 1). This function is capable of dealing with both negative and non-negative input values.
  4. Have a for loop to output column 1 and 2 using the corresponding column stream widths computed upfront (step 3 above).

The Project

I have decided to partition the program as follows – for practice sake.

C++ Source Files

  • main.cpp – this is the first program that is run during the implementation phase.
  • getStreamWidth.cpp – contains all functions relating to obtaining the stream widths.

C++ Header Files

This diagram below shows what the Code::Block Management Tree look like after successful creation of these files.

Acpp4p3MgntTree

The actual content of the source and header files are documented in the following sections.

Source Files

main.cpp

#include <iostream>
#include <ios>
#include <iomanip>
#include <algorithm>
#include "getStreamWidth.h"

using std::cout;  // <iostream>
using std::cin;   // <iostream>
using std::endl;  // <iostream>
using std::streamsize;  // <ios>
using std::setw;  // <iomanip>
using std::max;  // <algorithm>

int main()
{
#include <iostream>
#include <ios>
#include <iomanip>
#include <algorithm>
#include "getStreamWidth.h"

using std::cout;  // <iostream>
using std::cin;   // <iostream>
using std::endl;  // <iostream>
using std::streamsize;  // <ios>
using std::setw;  // <iomanip>
using std::max;  // <algorithm>

int main()
{
    // display program intro message
    cout << "***********************************************************\n"
         << "*** This program computes the square of the numbers     ***\n"
         << "*** in the asymmetric range [m,n).                      ***\n"
         << "*** (Limitation: please ensure n > m)                   ***\n"
         << "*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***\n"
         << "***********************************************************";
    cout << endl;

    // ask user to supply m
    cout << "Enter m: ";
    int m;
    cin >> m;

    // ask user to supply n
    cout << "Enter n: ";
    int n;
    cin >> n;

    // ensure m and n are input correctly. If not, exit program.
    if (n <= m)
    {
        cout << "Please make sure n > m";
        return 1;
    }

    // initialise value
    const int startNumber = m;    // first output integer
    const int endNumber = n - 1;  // last output integer
    const int numLoops = n - m;  // number of rows to output

    // find the maxwidth for column 1 and 2
    const streamsize col1Width = max(getStreamWidth(startNumber), getStreamWidth(endNumber));
    const streamsize col2Width = max(getStreamWidth(startNumber * startNumber), getStreamWidth(endNumber * endNumber));

    // display a summary
    cout << "Asymmetric range: [" << m << "," << n << ")" << endl;
    cout << "Number of rows = " << numLoops << endl;
    cout << "Column 1 width = " << col1Width << " | Column 2 width = " << col2Width << endl;

    // get ready to print report
    int y = startNumber;
    for (int i = 0; i != numLoops; ++i)
    {
        cout << setw(col1Width) << y << setw(col2Width) << (y * y) << setw(0) << endl;
        ++y;
    }
  return 0;
}

getStreamWidth.cpp

#include <ios>

using std::streamsize;

// return the required streamsize to fit a particular integer number
streamsize getStreamWidth(int number)
{

    streamsize numDigits;

    // initialise numDigits and number depending on whether value is positive / negative.
    // If negative, require at least 2 spaces to fit the leading empty space string and the negative sign
    // If positive, require at least 1 space to fit the leading empty space string
    if (number < 0)
    {
        numDigits = 2;
        number *= -1;
    }
    else numDigits = 1;

    // numDigits is the number of divisions required to make number approaches zero (plus leading space and sign)
    // i.e. this is equivalent to the total stream width required
    while (number != 0)
    {
        ++numDigits;
        number /= 10;
    }

    return numDigits;
}

Header Files

getStreamWidth.h

#ifndef GUARD_GETSTREAMWIDTH_H
#define GUARD_GETSTREAMWIDTH_H

std::streamsize getStreamWidth(int number);

#endif // GUARD_GETSTREAMWIDTH_H

Test Results

Asymmetric range [-3, 4)

***********************************************************
*** This program computes the square of the numbers     ***
*** in the asymmetric range [m,n).                      ***
*** (Limitation: please ensure n > m)                   ***
*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***
***********************************************************
Enter m: -3
Enter n: 4
Asymmetric range: [-3,4)
Number of rows = 7
Column 1 width = 3 | Column 2 width = 2
 -3 9
 -2 4
 -1 1
  0 0
  1 1
  2 4
  3 9

Asymmetric range [995,1006)

***********************************************************
*** This program computes the square of the numbers     ***
*** in the asymmetric range [m,n).                      ***
*** (Limitation: please ensure n > m)                   ***
*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***
***********************************************************
Enter m: 995
Enter n: 1006
Asymmetric range: [995,1006)
Number of rows = 11
Column 1 width = 5 | Column 2 width = 8
  995  990025
  996  992016
  997  994009
  998  996004
  999  998001
 1000 1000000
 1001 1002001
 1002 1004004
 1003 1006009
 1004 1008016
 1005 1010025

Asymmetric range [-1005,-996)

***********************************************************
*** This program computes the square of the numbers     ***
*** in the asymmetric range [m,n).                      ***
*** (Limitation: please ensure n > m)                   ***
*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***
***********************************************************
Enter m: -1005
Enter n: -996
Asymmetric range: [-1005,-996)
Number of rows = 9
Column 1 width = 6 | Column 2 width = 8
 -1005 1010025
 -1004 1008016
 -1003 1006009
 -1002 1004004
 -1001 1002001
 -1000 1000000
  -999  998001
  -998  996004
  -997  994009

Asymmetric range [0,1000)

***********************************************************
*** This program computes the square of the numbers     ***
*** in the asymmetric range [m,n).                      ***
*** (Limitation: please ensure n > m)                   ***
*** e.g. [3,7) contains elements 3, 4, 5, 6 (but not 7) ***
***********************************************************
Enter m: 0
Enter n: 1000
Asymmetric range: [0,1000
Number of rows = 1000
Column 1 width = 4  Column 2 width = 7
   0      0
   1      1
   2      4
   ...
 993 986049
 994 988036
 995 990025
 996 992016
 997 994009
 998 996004
 999 998001

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-2

Exercise 4-2

Write a program to calculate the squares of int values up to 100. The program should write two columns: The first lists the value; the second contains the square of that value. Use setw to manage the output so that the values line up in columns.

Solution

To keep the code as simple as possible, for the sake of solving this particular problem, I will have some kind of hard-code values in the codes. Of course, the code can be made more intelligent later on and remove the need of hard-coding!

The setw(n) function returns a value of type streamsize that, when written on an output stream s, has the effect of calling s.width(n).

After doing some experiments in Code::Block I learn that, if (say) the integer width is smaller than n, the outputstream will simply align the numbers to the right, and pad the left side with leading empty spaces. Likewise for strings.

We are asked to output two columns.

Column 1 contains a list of integers defined by this symmetric range [0,100] – i.e. between 0 and 100 inclusively. We therefore require n = 3 for this, so we can fit in the max integer 100.

Column 2 contains a list of integers defined by this symmetric range [0, 10000] – i.e. between 0 and 10000 inclusively. We therefore require n = 6 for this, so we can fit in the max integer 10000 (which takes up a width of 5), plus 1 leading empty space to separate this column from column 1.

The program will look like this:

#include <iostream>
#include <iomanip>

using std::cout;
using std::endl;
using std::setw;

int main()
{
    for (int i = 0; i != 101; ++i)
    {
        cout << setw(3) << i << setw(6) << (i * i) << endl;
    }
}

Result

  0     0
  1     1
  2     4
  3     9
  4    16
  5    25
  6    36
  7    49
  8    64
  9    81
 10   100
 11   121
 12   144
 13   169
 14   196
 15   225
 16   256
 17   289
 18   324
 19   361
 20   400
 21   441
 22   484
 23   529
 24   576
 25   625
 26   676
 27   729
 28   784
 29   841
 30   900
 31   961
 32  1024
 33  1089
 34  1156
 35  1225
 36  1296
 37  1369
 38  1444
 39  1521
 40  1600
 41  1681
 42  1764
 43  1849
 44  1936
 45  2025
 46  2116
 47  2209
 48  2304
 49  2401
 50  2500
 51  2601
 52  2704
 53  2809
 54  2916
 55  3025
 56  3136
 57  3249
 58  3364
 59  3481
 60  3600
 61  3721
 62  3844
 63  3969
 64  4096
 65  4225
 66  4356
 67  4489
 68  4624
 69  4761
 70  4900
 71  5041
 72  5184
 73  5329
 74  5476
 75  5625
 76  5776
 77  5929
 78  6084
 79  6241
 80  6400
 81  6561
 82  6724
 83  6889
 84  7056
 85  7225
 86  7396
 87  7569
 88  7744
 89  7921
 90  8100
 91  8281
 92  8464
 93  8649
 94  8836
 95  9025
 96  9216
 97  9409
 98  9604
 99  9801
100 10000

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000

Accelerated C++ Solution to Exercise 4-1

Exercise 4-1

We noted in S4.2.3/65 that it is essential that the argument types in a call to max match exactly. Will the following code work? If there is a problem, how would you fix it?

int maxlen;
Student_info s;
max(s.name.size(), maxlen);

Solution

If we refer back to the program in S4.2.3/65, s.name.size() is of type std::string::size_type. If we call the std::max function with argument 1 (s.name.size()) that is of std::string::size_type, and argument 2 (maxlen) that is of type int, we are essentially comparing variables of different types. We are “comparing apple with orange”! The program will bump into compilation failure and complain.

To amend this, just simply make maxlen the same type as s.name.size(). We assume at the top of the program we already have all the required using std::xxx declarations.

string::size_type maxlen;
Student_info s;
max(s.name.size(), maxlen);

Now, both arguments 1 and 2 that we supply to the max function are of the same type string::size_type. i.e. We are now “comparing apple with apple”. The program should now compile.

Proof of Concept

Seeing is believing. I now provide the tangible evidence of why the above debate is so. To do this, I create a very simple program that simulates that “invalid” code in question, then compile it, then see compilation error, then go and fix it, then re-compile it, and demonstrate no compilation error after fixing.

Before the Fix

For instance, I explicitly specify the Student_info object type here.

#include <iostream>
#include <string>
#include <algorithm>

struct Student_info
{
    std::string name;
};

int main()
{
    int maxlen;       // this causes error
    Student_info s;
    std::max(s.name.size(), maxlen);
    return 0;
}

Submitting this program in an IDE (e.g. Code::Block) for compilation return the following errors:

error: no matching function for call to ‘max(std::basic_string<char>::size_type, int&)’

The compiler detects that we are comparing two different types and complain accordingly.

After the Fix

We can correct this easily by changing the int maxlen to std::string::size_type maxlen. i.e. corrected program as following:

#include <iostream>
#include <string>
#include <algorithm>

struct Student_info
{
    std::string name;
};

int main()
{
    std::string::size_type maxlen;   // this works
    Student_info s;
    std::max(s.name.size(), maxlen);
    return 0;
}

Submitting this corrected program the IDE now compiles smoothly.

Reference

Koenig, Andrew & Moo, Barbara E., Accelerated C++, Addison-Wesley, 2000