Changelog:
- 19 Apr 2023: correct reference to CSO1 C++ lab; remove requirement for mixed-item initializer list not to compile
We showed you some basic C++ last semester, including using templates like Java uses generics. But that is far from all C++ templates can do… This lab will help you explore this topic in more detail.
We recommend that you work in pairs for this lab, like those before it.
1 Caveat
There exists a subset of programmers who believe that C++ templates beyond type generics are an abomination and should never be used. I hope this lab will help you make your own opinion, but be aware that if you work for a company that uses C++ and suggest some of these techniques, you might get some blow-back…
2 Review
Begin by reviewing the C++ lab from CSO1.
We’ll be picking up from where those left off in this lab.
3 A Mathematical Vector type
In mathematics, a vector has fixed length and adding vectors of different length is meaningless. In this lab you’ll use C++ templates to make a mathematical vector class that uses the type checker to ensure at compile time that no wrong-sized operations occur.
Implement a templated mathematical vector type in C++. When you are
done, the following code should do as the comments suggest (you’ll need
to comment out the does not compile
lines to get the others to
work):
typedef vec<double, 2> vd2;
typedef vec<double, 3> vd3;
typedef vec<int,4> vi4;
int main() {
;
vd3 astd::cout << a << std::endl; // prints (0, 0, 0)
[2] = 2.5;
astd::cout << a << std::endl; // prints (0, 0, 2.5)
std::cout << (a + a) << std::endl; // prints (0, 0, 5)
std::cout << a << std::endl; // prints (0, 0, 2.5)
;
vd2 bstd::cout << b << std::endl; // prints (0, 0)
;
vi4 cstd::cout << c << std::endl; // prints (0, 0, 0, 0)
std::cout << (a + b) << std::endl; // does not compile
std::cout << (a + c) << std::endl; // does not compile
[2] = 2.5; // implicitly casts a double to an int...
c[1] = 2.5;
b[2] = 2;
c
std::cout << b << std::endl; // prints (0, 2.5)
std::cout << c << std::endl; // prints (0, 0, 2, 0)
= {1.5, 2.5, 3.5};
vd3 x std::cout << x << std::endl; // prints (1.5, 2.5, 3.5)
= {1, 2, 3};
vd3 y std::cout << y << std::endl; // prints (1, 2, 3)
}
You code should not contain any heap-allocated memory (no
new
or malloc
)
When you’re done submit your C++ files to the submission site, OR show your TA what you’ve done for check-off.
4 Guides
Precede your
struct
(orclass
) withtemplate <typename N, int n>
so it will work with multiple types and lengths. (The only difference betweenstruct
andclass
in C++ is whether it defaults topublic
orprivate
.)Use a static array
N data[n]
(or the like) as the only field so avoid heap memory allocationsConstructors have no return type and the same name as the
struct
- your default constructor should sets the data to all
0
s - the other should accept an
initializer list
, which is what the{1,2,3}
turns into at compile time#include <initializer_list>
- 1 argument, a
std::initializer_list<R>
whereR
is the type of value you expect to be passed in. - a
std::initializer_list<R>
is a collection, meaning you access it by iterator - you’ll need to verify that the initializer list has the right number
of values (it’s
size()
method should help) - we recommend using a template to pick the initializer list contained
type, e.g. with
template<typename R>
before the function
- your default constructor should sets the data to all
Operator overloading uses special function names
operator +
should accept only vectors of the same lengthoperator []
needs two variantsN& operator[] (int idx)
– allows setting by index by returning a referenceN operator[] (int idx) const
– allows reading when no reference exists
- Printing needs a special
friend
notation to let you access between theostream
andvec
classes:friend std::ostream& operator << (std::ostream& out, const vec<N,n>& x)
- this function should use
out << thing
and thenreturn out
It is best practice (but not technically required) to
put
template
declarations on the line before the struct or function they modifytemplate <typename R> (const R& t) { /*...*/ } R dostuff
use
const
for all arguments you will not modifydouble sqrt(const double x)
use reference types
const mytype&
forstruct
arguments you don’t want to copydouble length(const vec<double,3>& x)
use
const
for any method that does not modifythis
’s fieldsdouble length() const
5 C++ Iterators
Many STL structures in C++ use the iterator pattern to allow access. This has a somewhat different look than it does in Java or the like.
The collection offers two functions of note:
.begin()
returns an iterator pointing to the first element of the collection.end()
returns an iterator pointing to thepast the end
element of the collection
The iterator overloads three operators of note:
operator !=
tells if two iterators point to distinct entries in the collectionoperator *
gets the item pointed to out of the iteratoroperator ++
moves the item to the next spot in the collection
Thus, a loop that prints all items in a collection might look like
for(auto it = mycollection.begin(); // create an iterator
!= mycollection.end(); // and while it's not off the end
it ++it) { // move it forward
std::cout << *it << std::endl; // derreference to print
}
Note in the above that when you combine a declaration and
initialization, you can declare the type to be auto
meaning
use whatever type the initialization gives me
.