Lab 3: templates, C++ Standard Library containers, iterators
General guidelines
Important! Read the guidelines on formatting from the course homepage! Tasks are submitted on the course homepage. Submissions by e-mail are not accepted. If you have any questions then write to the list or a lab instructor.
There are 14 days for solving the task.
Deadline: 24.03.2022 23:59:59
NB: As of this practice, documentation with Doxygen is no longer mandatory. Students will be expected to have good code in the future.
Task 1 – Geometry in n-dimensional space.
In this lab we write the most complete point, line and sphere classes so far. We use class templates to create geometrical objects in n-dimensional space (note: your code should give trivial answers even when initialized with 0-dimensions). To create the following classes use templates.
If you are not going to have any cpp-files, then do not create a ‘.a’ geometry-library file. In the first task that may be the ćase.
1.1 Universal point (point.h
) (3 points)
Create a class Point
, to which you can give the number of coordinates (template <unsigned short n>
). The given non-negative integer n specifies how many dimensions the point has. Save
the point's coordinates to STL container class std::list
into a variable named coords
.
Some examples:
Point<2> twoDimensionPoint; // point on a plane Point<10> tenDimensionPoint; // point in 10 dimensions
Add following methods to the class:
Method | Task |
---|---|
Point<n> () | Initializes coordinates with 0’s |
Point<n> (list<float> crds) | Initializes coordinates with given values |
float distanceFrom (Point<n> v) | Returns the distances from another same-dimension point |
string toString () | Returns point's string representation (x1, x2, ..., xn ) |
operator << | Outputs point's info (use toString method) |
A friend
keyword is helpful in overloading the output stream shift operator .
NB! Using a friend
keyword inside templates can cause weird compilation errors or warnings. See also https://isocpp.org/wiki/faq/templates#template-friends.
NB! Overloading an operator is easier if the Point
object specified as a parameter is not constant. See the tips on the last page to write a nicer solution.
If error conditions occur, such as if the number n of coordinates specified in the template differs from the size of the predefined coordinate vector, throw string-typed exception (see Material). Write a human-readable text as an exception text on what went wrong. Note that 0-dimension point is legal and is not an error situation.
1.2. Universal straight section (line.h
) (1 point)
Create a class template Line
with a class parameter T
(template <class T>
). A class represents a straight line over points of any dimension. The class has two attributes - p1
and p2
, both are of type T
- they represent the vertices of a straight line. As a result, it must be possible to create lines with two vertices as follows:
Line< Point<2> > kahem66tmelineSirgl6ik; Line< Point<7> > seitsmem66tmelineSirgl6ik;
Add methods to the class:
Method | Task |
---|---|
Line <T> () | default constructor - creates a line (using <T> default constructor) |
Line <T> (T np1, T np2) | parameter constructor - initialized class attributes |
float length () | returns the length of a straight line using the class T method distanceFrom |
string toString () | returns a string representation of the line ((point1)-(point2)) |
operator << | outputs line data (use toString method) |
1.3. Universal Sphere ( sphere.h) (3 points)
Create a class template Sphere
with a point class parameter T
(template <class T>
). The class represents spheres (and, in the special case of two-dimensios circles). The class should have a T
type attribute o
that represents the midpoint and a float
attribute r
that represents the radius. The radius should never be negative.
As a result, it must be possible to make circles and spheres as follows:
Sphere< Point<2> > ring; Sphere< Point<3> > kera;
Method | Task |
---|---|
Sphere () | default constructor - creates a T type point and sets the radius to zero |
Sphere <T> (T no, float nr) | parameter constructor - uses the given point and radius |
bool contains (T v) | returns true if the point v is on or inside the sphere, otherwise false |
bool contains (Line <T> l) | returns true if this line is inside the sphere, otherwise false |
void scale (float factor) | multiplies the radius of the sphere by factor |
string toString () | returns the sphere as a string ((point), radius) |
operator << | outputs sphere data (use toString method) |
This is not part of the task, but keep in mind that there is also an option in the language to write implementations with specific parameters. For example, you can realize the calculation of the circumference and area for a two-dimensional sphere (circle) and the volume for a three-dimensional sphere as special cases. The corresponding keyword to look into is template specialization.
NB! Do not change the suggested variable names and keep the variables public. This makes testing easier. Also create a header file geometry.h
that adds the headers of the point, line, and sphere classes.
Task 2 - Universal Polygon Class (3 points)
As a result of the changes, it is necessary to create a class template Polygon
with parameter class T
and an integer n
(template <class T, unsigned short n>
). The class represents n-point polygons over given points. Add these methods to the class template:
Method | Task |
---|---|
Polygon <T, n> () | initializes points with zeros |
Polygon <T, n> (vector <T> pts) | sets the coordinates of the vector to the given values |
float perimeter () | gives the perimeter of the polygon (sum of the lengths of the sides) |
string toString () | returns a polygon as a string ((point1),...,(pointn)) |
operator << | outputs polygon data (use toString method) |
Hints
For overloading the output stream operator <<
, first see the “Read more about uploading the output flow routing operator” material in the second lab.
If a parameter of a function is aconstant, when passing that parameter to another function it must remain a constant. For example, if the class is described as follows
class Vector2 { ... string toString(); friend ostream& operator<<(ostream& out, const Vector2& vec); }
Then the operator overload cannot be realized this way
ostream& operator<<(ostream& out, const Vector2& vec) { out << vec.toString(); // veateade return out; }
The problem is that the compiler does not know what the toString
method does and whether that method changes the Vector2
parameter. The solution is to write const
at the end of the method (string toString() const;)
, which ensures that the parameter of that method is not modified and allows you to call toString
on constant variables.
Additional Task 3 - Function Objects or Lambda Functions (1 additional point)
General requirements
The third task is for you to choose between two exercises. You can solve 3.1 or 3.2 or both, but points can only be earned for one. When presenting both additional tasks, the first or function object exercise is assessed. As you complete the task, you can practice working with algorithms and function objects. The resulting code (if any) should be in a library file with the name libmyfunctors.a
and header myfunctors.h
. To test the solution, we use a program that runs the code you write and checks that the results meet the requirements of the task. We recommend that you do a test program where you try out your own solution. The test program is not evaluated.
Place the solution in the same directory tree as the geometry code for the first problem. By default, the makefile must build a ready-made library.
Part 3.1 - Function objects or functors
A function object with state and one parameter to sum elements in a container
A quick introduction to the topic of status functors. Functions can also be defined before they are implemented. This provides a nice additional opportunity to use them, for example, in collecting values. The functor is created before starting the algorithm, and after the work is completed, the result is read inside the object. To support that the functor must have, besides the operator ()
, required variables and methods must be defined. Your task is to write a class
template SumElements<T>
with class parameter T
, which could be used to sum containers
containing T
type elements. We assume that type T
has implemented the operator +
(e.g. int, float, string). If the object is tried to be used with a type which does not have the operator implemented, an error will occur.
The function object could be implemented in a header. An example on how the object should be able to be used:
vector<unsigned long> values; // vector of long SumElements<unsigned long> addThisValue; // functor of same type addThisValue = for_each(values.begin(), values.end(), addThisValue); // sum unsigned long sum = addThisValue.result (); // read the sum
The solution must work for both numeric types and words. Tip: Using curly parentheses when creating an object resets numeric types to zero and words to empty words. For example
int x; // x’s value can be anything int x{}; // x is initialized to 0 int x(); // Compiler considers this a function declaration
Part 3.2 - Aggregation of container elements using the lambda function
A lambda or lambda function is a function that can be used to create a single function object, such as for algorithms. Therefore, there is no need to define a separate function object for the algorithm, which in turn saves time and reduces code. The body of the lambda function usually consists of three parts:
[capture_clause] (arguments) {expression}
// capture_clause - Variables that may be needed in the expression, such as to store the amount. Only captured variables can be used // arguments - Arguments given to a lambda expression (similar to a function). // expression - An expression for the lambda function that defines behavior. // Variables can also be captured individually by reference or value as needed. // To capture all scope variables by reference, use [&] // Examples of lambda functions [& a] (int x, int y = 1) {return a - = x * y; } // We only capture 1 scope variable by reference (a). [&] (int x) {return a - = x + b; } // We capture all scope variables (a, b) by reference. [a] (int x, int y = 1) {return a - = x * y; } // Let's capture one scope variable (a) by value. [ = ] (int x, int y = 1) {return a - = x * y + b; } // We capture all scope variables by (a, b).
The lambda function actually returns a function object, or functor.
Example of using the lambda function:
vector<int> arvuvektor {32,71,12,45,26,80,53}; // define the vector with numbers // The third argument: "[] (int x) {return (x% 2 == 0);}" is a lambda function // Local scope variables are not used, so no variables are captured. partition (arvuektor .begin (), arvuvektor.end (), [] (int x) {return (x% 2 == 0);}); // result: 12.26, 32, 80, 45, 53, 71
The task is to write a template function that accepts vectors of type T
and return their sum. Assume that elements of the type T
have a defined operator +
and can also be shifted to the output stream. The function should be implemented using for_each
i and the appropriate lambda function. The body of the template function is as follows:
template <typename T> T summeeri_malli_vektor( vector<T> vec ) { // Implement function }
The template function could be implemented in the header. An example of how a feature should be available.
vector<string> values {"a","b","c","d","e"}; // vector of letters / words vector<int> values1 {5,4,3,2,1}; // vector of ints vector<float> values2 {5.0f,4.0f,3.0f,2.0f,1.0f}; // vector of floats cout << "Sum is: " << summeeri_malli_vektor(values) << endl; // prints "Sum is abcde" cout << "Sum is: " << summeeri_malli_vektor(values1) << endl; // prints "Sum is 15" cout << "Sum is: " << summeeri_malli_vektor(values2) << endl; // prints "Sum is 15.000000"
Additional information:
https://en.cppreference.com/w/cpp/language/lambda
http://en.cppreference.com/w/cpp/language/value_initialization
http://en.cppreference.com/w/cpp/language/default_initialization