CS235 Unit 06 Exercise: Templates

Table of Contents


1. Introduction

About
Templates are a way we can write functions and classes that work with a "placeholder" data type, instead of hard-coding a data type. These can be useful when we want to create a container that can store any data type, or otherwise implement some kind of function that can operate on any data type.
Goals
  • Practice creating a templated function
  • Practice creating a templated storage class
Setup
Starter files are available here: https://gitlab.com/moosadee/courses/-/tree/main/wip_exercises/starter_code/c3_u06_Templates?ref_type=heads

2. Project setup

2.1. CS 235 vs. CS 250

CS 235
I have not created personal repositories for students in CS 235. If you would like, I can set it up for you, OR you can create your own repository (and you can skip the merge request part), OR you can forego using a repository and turn in your code files on Canvas.
CS 250
CS 250 students have a personal repository for the class. Please follow the steps to create a new branch and to create a merge request at the end.

2.2. Creating a new branch

BEFORE getting started on the assignment, make sure to go back to the main branch and pull latest. This way, you will have the most up-to-date version of your repository, and you'll be starting from a shared base-point.

  1. git add . && git commit -m "backup" && git push to make sure to backup any work on your current branch.
  2. git checkout main to check out the main branch again.
  3. git pull to pull latest changes from the server.

Next, you will create a new branch to get started from:

  1. git checkout -b u06ex to create a new branch for u05ex.

2.3. Starter code files

Use your IDE to create a new project. Make sure you're putting the project inside your repository directory! Also, the project should have the exercise number in the name, such as "U06 Templates".

Use the starter code located here: https://gitlab.com/moosadee/courses/-/tree/main/wip_exercises/starter_code/c3_u06_Templates?ref_type=heads add the files (except the Makefile) to your project.

2.4. About the project

This project includes several distinct parts, with different ways to show how templates can be used.


3. Observation: Display

Within the Functions.h file you will see the templated function, Display:

template <typename T>
void Display( const vector<string>& header, const vector<vector<T>>& data )
{
    int col_width = 80 / header.size();
    cout << left << fixed << setprecision( 2 );
    for ( auto& head : header )
    {
        cout << setw( col_width ) << head;
    }
    cout << endl << string( 80, '-' ) << endl;
    for ( auto& row : data )
    {
        for ( auto& col : row )
        {
            cout << setw( col_width ) << col;
        }
        cout << endl;
    }
}

This function is already implemented, and its purpose is to write out a table of data. We don't want to have to make duplicate functions for "a table of integer data", "a table of float data", "a table of string data", so we use templates to make one function template that instead deals with a placeholder type T - "a table of T data".

The item using the template is the input parameter, const vector<vector<T>>& data. It might look a little weird - a vector of vectors of T objects. Basically, it is an array of rows, and then each row contains an array of columns of data. You don't really have to worry about the specifics here, since the function is already implemented. The main idea is that the data could be any data type, so the placeholder T is being used.

If you run the program, you can see the result of the Display test function:

-------------------- Test_Display (Manual tests) --------------------
Test 1 - Classes
Class 1             Class 2             Class 3             Class 4             
--------------------------------------------------------------------------------
CS 134              CS 200              CS 235              CS 250              
ASL 120             ASL 122             ASL 135             ASL 145             


Test 2 - Minimum wage vs. Median monthly rent
2010       2000       1990       1980       1970       1960       1950       
--------------------------------------------------------------------------------
7.25       5.15       3.80       3.10       1.60       1.00       0.75       
841.00     602.00     447.00     243.00     108.00     71.00      42.00      

4. Templated function: Add

Now we will implement the Add function, a simple function that adds two items of any data type together (as long as the + operation has been implemented for it). You will also write two unit tests to check your work.

A templated function definition must go in the .h file, not the .cpp file. Technically, a templated function is not C++ code, but a special type of code that the compiler uses to generate new functions… So while we only write one version of "Add", the compiler will see all usages of our function and create an "int version", "float version", etc. based on what is going to be used.

Templated functions take this form:

template <typename T>
RETURNTYPE FUNCTIONNAME( PARAMETERS )
{
}

4.1. Add function

The Add function will have a T return type, and take in two T parameters. Within the function, add the two parameters together and return the result.

4.2. Add unit test

Declare the tester function here in the .h file. The tester function isn't templated, so the declaration will go here and its definition will go in Functions.cpp.

void Test_Add();

For the function definition, create at least two tests. One should take in integer inputs and one should take in float inputs, and make sure the math checks out for both. For example:

Test Inputs Expected output
1 2.5, 3.25 5.75
2 3, 7 10

The form I use for my unit tests is as follows:

{ // test begin
  float input_a = VALUE;
  float input_b = VALUE;
  float expected_result = VALUE;
  float actual_result = FUNCTIONNAME( input_a, input_b );

  string test_name = "description";
  sring result = "";

  if ( actual_result != expected_result ) { result = "[FAIL] "; }
  else { result = "[PASS] "; }

  // Use cout to display the test name, result, and the inputs / expected result / actual result.
} // test end

4.3. Starting the test in main()

Make sure that you're calling your test from within the main() function as well. Build and run the project to make sure everything is running and operating as intended.


5. Templated function in a class: Log

The Log class in Log.h is not a templated class, but it contains a templated member function: void Log::Out( string name, T value ).

The purpose of this Log class would be for logging a program's functionality as it runs, as well as variable values. The Out function takes in a name (such as the variable's name) and then a value, which could be any data type. Then that data is written out to a log file.

The unit test for the Out function is already written. Just implement the Log::Out function by writing out the name and value to the output file, which is the variable named m_log. (You can treat m_log like you would cout.)


6. Templated class: SmartFixedArray

Finally, one of the more common uses of templates - a templated structure that stores data. A data structure.

Within SmartFixedArray.h a templated class is already created, and unit tests already exist for the class. You will just be implementing the member functions.

The SmartFixedArray has the following member variables:

  • T m_array[100] - an array of "some data type".
  • const int ARRAY_SIZE - a named constant that stores the value 100, the size of the array.
  • int m_itemCount - The amount of items currently stored in the array. Starts at 0, and is added to each time a new value is added.

6.1. void SmartFixedArray<T>::Clear()

The clear function will "lazy-delete" the contents of the array. This means that we set it up so that it looks empty to the outside and when new items are added they overwrite any old data.

Basically, the implementation here is to set the m_itemCount to 0.

6.2. int SmartFixedArray<T>::Size() const

This function returns how many items are currently stored in the array. So you will be returning the value from m_itemCount.

6.3. bool SmartFixedArray<T>::IsFull() const

This returns whether the array is full or not. Remember that ARRAY_SIZE contains the amount of total "slots" in the array, and m_itemCount contains how many items are currently stored within. The array is full if these two values equal each other.

6.4. void SmartFixedArray<T>::PushBack( T newItem )

This function will add a new item to the array. New items will need to go at the first available spot, such as index 0, then 1, then 2, etc. The new data that will be saved comes in the form of the input parameter, newItem.

Error check: First off, we need to do an error check: If the array is full, then throw an exception - we cannot add any more items!

Normal functionality: Remember that to set an array element's value, we use this form:

ARRAYNAME[ INDEX ] = VALUE;

Our array is the m_array, the value will be the newItem, and the index will be the next available space… which also happens to be the value of m_itemCount.

After adding the item to the array, make sure to increment m_itemCount by 1 as well.

6.5. void SmartFixedArray<T>::PopBack()

The PopBack function will remove the element currently at the end of the list of items in the array.

Error check: If the array is empty, we cannot remove any items. Throw an exception in this case.

We are one again lazy deleting. To do this, we just decrement m_itemCount by 1.

6.6. T& SmartFixedArray<T>::GetAt( int index )

This function will return the element of the array at the index position.

Error check: If the array is empty, throw an exception; we cannot return anything!

Error check: If the index is invalid (less than 0 or greater than or equal to the m_itemCount), then throw an exception.

Otherwise, return the element of m_array at the index provided.

6.7. Unit tests

Once finished, run the program. Unit tests will run. Make sure everything is passing.


7. Example completed output

TEMPLATE EXERCISE


-------------------- Test_Add --------------------
[FAIL] Add two floats and check the result
* input_a          = 2.5
* input_b          = 3.25
* expected_result  = 5.75
* actual_result    = -0.75

[FAIL] Add two ints and check the result
* input_a          = 3
* input_b          = 7
* expected_result  = 10
* actual_result    = -4


 Press ENTER to continue...

-------------------- Test_Display (Manual tests) --------------------
Test 1 - Classes
Class 1             Class 2             Class 3             Class 4             
--------------------------------------------------------------------------------
CS 134              CS 200              CS 235              CS 250              
ASL 120             ASL 122             ASL 135             ASL 145             


Test 2 - Minimum wage vs. Median monthly rent
2010       2000       1990       1980       1970       1960       1950       
--------------------------------------------------------------------------------
7.25       5.15       3.80       3.10       1.60       1.00       0.75       
841.00     602.00     447.00     243.00     108.00     71.00      42.00      

 Press ENTER to continue...

-------------------- Test_Log --------------------
[PASS] Log variable values and check results
Input data:
* data1          = 3
* data2          = 3.14
* data3          = cheese
File contents:
* buffer1        = data1: 3
* buffer2        = data2: 3.14
* buffer3        = data3: cheese


 Press ENTER to continue...

Running testset 1 out of 6:   Constructor()
[PASS] 0a. Check if functions are implemented
[PASS] 1. Check initial member variable values

Running testset 2 out of 6:   PopBack()
[PASS] 0. Check if function PopBack is implemented
[PASS] 1. Set up array, remove back
[PASS] 2. ERROR CHECK: Check for exception with empty array

Running testset 3 out of 6:   GetAt()
[PASS] 0. Check if function GetAt is implemented
[PASS] 1. Set up array, get at 1
[PASS] 2. ERROR CHECK: Check for exception with invalid index
[PASS] 3. ERROR CHECK: Check for exception when array is empty

Running testset 4 out of 6:   IsFull()
[PASS] 0. Check if function IsFull is implemented
[PASS] 1. Check if IsFull returns true when full
[PASS] 2. Check if IsFull returns false when not full

Running testset 5 out of 6:   IsEmpty()
[PASS] 0. Check if function IsEmpty is implemented
[PASS] 1. Check if IsEmpty returns false when an item is in the array
[PASS] 2. Check if IsEmpty returns true when empty

Running testset 6 out of 6:   PushBack()
[PASS] 0a. Check if function PushBack is implemented
[PASS] 1. Add three items, check they're in the correct position
[PASS] 2. ERROR CHECK: Check if StructureFullException is thrown if array ...

- RESULTS ------------------------------------------------------------
- Total tests:           6
- Total tests passing:   6
- Total tests failing:   0
- 
- NOTE: CHECK "test_result_smart_fixed_array.html" in the directory --
- - for full information on test results,
- including expected output vs. actual output and notes.
----------------------------------------------------------------------


8. Turning in your work

CS 235
You can turn in your work by uploading your code files, or if you want to use a repository you can also submit the URL to where your project is being stored.
CS 250
Make sure to add, commit, and push your work to your u06ex branch.

Afterwards, go to the GitLab repository page and create a Merge Request. The URL of the merge request is what you will turn in on Canvas.


Author: Rachel Wil Sha Singh

Created: 2023-09-15 Fri 02:05

Validate