CS253: Software Development with C++

Spring 2022

HW 7

CS253 HW7: Ranges!                

Changes                

A line in the example code was wrong. Old bad line:

    assert(Range(12,34)==a && s == "foobar");

new good line:

    assert(Range<int>(12,34)==a && s == "foobar");

I don’t know why the old bad line compiled for me, but it’s not required to work.                 

Description                

Frequently, in computation, it is useful to represent a range of possible values: prices, ages, heart rates, etc. For this final assignment, you write a templated class called Range, with a capital R (no relation to C++20 std::ranges). A Range represents a closed (inclusive) interval [a,b ]. For example, Barack Obama was U.S. President [2009,2017], because he was inaugurated on January 20, 2009, and served until January 20, 2017.                 

A Range contains two numbers (based on the template parameter), representing the range. The first number must be ≤ the second number. This writeup will use [a,b ] to represent a Range, but that’s just mathematical notation, and the input/output format. You can’t actually have a statement r = [3,4]; // 🦡                 

Two Range objects can be combined, through arithmetic operations. The resulting Range represents the limits of the results of the computation. For example:

The first example means:

The resulting Range must be in order (first ≤ second).                 

This is not probability. This is not a bell-shaped curve. This is a simple range of possibilities.                 

A Range has a template parameter, which must be a numeric type, either integer or floating-point. This type must be used for the storage of the two values, and for all computations.                 

Methods and operations                

Range<T> must have these public methods & operators:                 

Range(T val)
This constructor initializes both values of the Range to val.
Range(T low, T high)
This ctor initializes the values of the Range to low and high. Throw a runtime_error object, mentioning both values, if the range is out of order.
copy ctor, assignment operator
Copy both values from the other object.
.assign(T low, T high)
Assign these values to the object. Throw a runtime_error object, mentioning both values, if the range is out of order.
.min()
Return the first (lower) number.
.max()
Return the second (higher) number.
Range1 += Range2
Add Range2 to Range1.
Range1 -= Range2
Subtract Range2 from Range1.
Range1 *= Range2
Multiply Range1 by Range2.
Range1 /= Range2
Divide Range1 by Range2. If Range2 contains zero, then throw a runtime_error object, mentioning the values in both objects.

The previous four methods put their result in Range1, and do not modify Range2.                 

Range1 + Range2
Add Range2 to Range1.
Range1 - Range2
Subtract Range2 from Range1.
Range1 * Range2
Multiply Range1 by Range2.
Range1 / Range2
Divide Range1 by Range2. If Range2 contains zero, then throw a runtime_error object, mentioning the values in both objects.

The previous four methods do not modify either Range1 or Range2. Instead, they yield the result by value.                 

Range1 == Range2
Yield true iff the objects contain the same values.
Range1 != Range2
Yield false iff the objects contain the same values.

For the +=, -=, *=, /=, +, -, *, /, ==, and != operators, the right-hand argument can also be a number of type T (the template parameter). This is treated as a singleton Range having both values of that number, as in the single-argument ctor.                 

These two operators must be implemented as free functions:                 

ostream << Range
Write the Range to the ostream. The << operator must yield a reference to the ostream. This will write exactly six bytes “[4,12]” to standard output:
        Range<unsigned> r(4,12);
        cout << r;
istream >> Range
Read the Range from the istream, in the same format as output above, with optional spaces before the brackets, numbers, and comma. The >> operator must yield a reference to the istream. failbit must be set if invalid input is encountered.

The types and names in the descriptions, above, do not determine the C++ declarations of those methods. They only serve to informally describe what sort of arguments a method might take. For example, == should return a bool, even though it wasn’t explicitly stated.                 

Const-correctness, for methods, arguments, and operators, is your job. For example, it must be possible call .min() on a const Range object, or compare two const objects.                 

You may define other methods or data, public or private, as you see fit.                 

Debugging                

If you encounter “STACK FRAME LINK OVERFLOW”, then try this:

    export STACK_FRAME_LINK_OVERRIDE=ffff-ad921d60486366258809553a3db49a4a

This is the Colorado State University CS253 web page https://cs.colostate.edu/~cs253/Spring22/HW7 fetched by unknown <unknown> with Linux UID 65535 at 2024-06-26T10:03:23 from IP address 3.138.143.234. Registered CSU students are permitted to copy this web page for personal use, but it is forbidden to repost the information from this web page to the internet. Doing so is a violation of the rules in the CS253 syllabus, will be considered cheating, and will get you an F in CS253.

Sample Run                

% cat CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(hw7)

# Are we in the wrong directory?
if (CMAKE_SOURCE_DIR MATCHES "[Hh][Ww]([0-9])$"
   AND NOT PROJECT_NAME MATCHES "${CMAKE_MATCH_1}$")
    message(FATAL_ERROR "Building ${PROJECT_NAME} in ${CMAKE_SOURCE_DIR}")
endif()

# Using -Wall is required:
add_compile_options(-Wall)

# These compile flags are highly recommended, but not required:
add_compile_options(-Wextra -Wpedantic)

# Optional super-strict mode:
add_compile_options(-fmessage-length=80 -fno-diagnostics-show-option
    -fstack-protector-all -g -O3 -std=c++17 -Walloc-zero -Walloca
    -Wctor-dtor-privacy -Wduplicated-cond -Wduplicated-branches -Werror
    -Wextra-semi -Wfatal-errors -Winit-self -Wlogical-op -Wold-style-cast
    -Wshadow -Wunused-const-variable=1 -Wzero-as-null-pointer-constant)

# add_compile_options must be BEFORE add_executable.

add_executable(test test.cc)

# Create a tar file every time:
add_custom_target(${PROJECT_NAME}.tar ALL COMMAND
    tar -cf ${PROJECT_NAME}.tar *.cc *.h CMakeLists.txt)
% cmake . && make
… cmake output appears here …
… make output appears here …
% cat test.cc
#include "Range.h"
#include <cassert>
#include <iostream>
#include <sstream>

using namespace std;

int main() {
    Range<int> a(4,20), b(500,900), c(504,920), d(80,90), e(1,6), f(74,89);
    const Range<unsigned short> g(80,90), h(2,5), i(16,45);
    Range<long long> j(5,7), k(-3,2), l(-21,14);
    const Range<long double> m(2.5,8.25);
    const long double n = -0.5;
    Range<long double> o(-4.125, -1.25);
    const Range<int> range_containing_zero(-2,1245183), zero(0);

    auto i_result = a; i_result += b;
    cout << a << " += " << b << " yields " << i_result << '\n';
    assert(i_result == c);

    i_result = d - e;
    cout << d << " - " << e << " = " << i_result << '\n';
    assert(i_result == f);

    auto us_result = g / h;
    cout << g << " / " << h << " = " << us_result << '\n';
    assert(us_result == i);

    auto ll_result = j; ll_result *= k;
    cout << j << " *= " << k << " yields " << ll_result << '\n';
    assert(ll_result == l);

    auto ld_result = m; ld_result *= n;
    cout << m << " *= " << n << " yields " << ld_result << '\n';
    assert(ld_result == o);
    assert(ld_result.min() == -4.125 && ld_result.max() == -1.25);

    stringstream ss("  [ 12 \t   ,34   \n  ]  foobar");
    string s;
    assert(ss >> a >> s);
    assert(Range<int>(12,34)==a && s == "foobar");

    bool caught = false;
    try {
        o.assign(5.001L, 4.999L);  // out of order
    }
    catch (const runtime_error &re) {
        cout << "Tried to assign out of order: " << re.what() << '\n';
        caught = true;
    }
    assert(caught);

    caught = false;
    try {
        f /= range_containing_zero;
    }
    catch (const runtime_error &re) {
        cout << "Tried to divide by zero: " << re.what() << '\n';
        caught = true;
    }
    assert(caught);

    assert(zero / a == zero);
    cout << "Done.\n";
}

static_assert(sizeof(Range<short>) == 2*sizeof(short));
static_assert(sizeof(Range<unsigned int>) == 2*sizeof(unsigned int));
static_assert(sizeof(Range<long double>) == 2*sizeof(long double));
% ./test
[4,20] += [500,900] yields [504,920]
[80,90] - [1,6] = [74,89]
[80,90] / [2,5] = [16,45]
[5,7] *= [-3,2] yields [-21,14]
[2.500000,8.250000] *= -0.5 yields [-4.125000,-1.250000]
Tried to assign out of order: out of order: [5.001000,4.999000]
Tried to divide by zero: divide by zero: [74,89]/[-2,1245183]
Done.

Hints                

Testing                

You will have to write a main() function to test your code. Put it in a separate file. Particularly, do not put main() in Range.h. We will test your program by doing something like this:                 

    mkdir new-directory
    cd new-directory
    tar -x </some/where/else/hw7.tar
    cmake . && make
    cp /some/other/place/test-program.cc .
    g++ -Wall test-program.cc
    ./a.out

We will supply a main program to do the testing that we want. You should do something similar. It’s your choice whether to include your test program in your hw7.tar file. However, cmake . && make must work. If it fails because you didn’t package test.cc, but your CMakeLists.txt requires test.cc, then your build failed, and you get no points. Test your tar file, not just your code.                 

Requirements                

If you have any questions about the requirements, ask. In the real world, your programming tasks will almost always be vague and incompletely specified. Same here.                 

Tar file                

    cmake . && make

How to submit your work:                

In Canvas, check in the file hw7.tar to the assignment “HW7”. It’s due 11:59ᴘᴍ MT Saturday, with a five-day late period.                 

How to receive negative points:                

Turn in someone else’s work.