| 1. | Introduction to C++ |
| 2. | Variables, branching, and loops |
| 3. | Functions and recursions |
| 4. | Pointers |
| 5. | Linked lists |
| 6. | Stacks |
| 7. | Sequences |
| 8. | Pointers practice |
| 9. | References |
| 10. | Sorting |
| 11. | Object oriented programming |
| 12. | Trees in C++ |
| 13. | Balanced trees |
| 14. | Sets and maps: simplified |
| 15. | Sets and maps: standard |
| 16. | Dynamic programming |
| 17. | Vectors |
| 18. | Multi core programming and threads |
| 19. | Representation of integers in computers |
| 20. | Floating point representation |
| 21. | Templates |
| 22. | Inheritance |
| 23. | Pointers to functions |
21. Templates
1. Function templates
1.1. Identical algorithms on different data types
Sometimes a program involves several functions that have identical algorithms and differ only in the types of arguments that they receive.
One such example is the function max2 that takes two arguments and returns the maximum of the two values.
If we need to apply the function max2 to integers and real numbers, then we would provide two versions.
#include<iostream>
long max2(long a, long b){
long biggerNum=a;
if(b>a){biggerNum=b;}
return biggerNum;
}
double max2(double a, double b){
double biggerNum=a;
if(b>a){biggerNum=b;}
return biggerNum;
}
int main(){
double d1, d2; long l1,l2;
std::cin>>d1>>d2>>l1>>l2;
std::cout<<max2(d1,d2)<<"\n";
std::cout<<max2(l1,l2)<<"\n";
return 0;
}
1.2. Programs with template functions
To make our source codes shorter, we can write one generic function that works on generic data type GeneralData instead of double and long. The precise syntax is given below
template <typename GeneralData>
GeneralData max2(GeneralData a, GeneralData b){
GeneralData biggerNum=a;
if(b>a){biggerNum=b;}
return biggerNum;
}
The above function can be implemented instead of multiple versions of the same function that differ only in the type of data. The functions can be now called in two ways:
- First way: Explicit identification of type.
std::cout<<max2<double>(d1,d2)<<"\n"; std::cout<<max2<long>(l1,l2)<<"\n";
- Second way: Implicit identification of type. If the compiler is capable of figuring out what is the type of data, then we can omit
<double>and<long>from the calls.std::cout<<max2(d1,d2)<<"\n"; std::cout<<max2(l1,l2)<<"\n";
Note: Sometimes the compiler cannot figure out which function to call. Then, the compiler will return an error. This is an example in which the error will be returned:
std::cout<<max2(19,29.11)<<"\n";
Create a generic function longestOfTheThree that accepts three objects of the same structure. It is known that the structure has a public attribute length. The function should return the object with the biggest value of the attribute length.
You should only write the code that replaces the text // ??? //.
#include<iostream>
struct River{
public:
std::string name;
double length;
};
struct Rectangle{
public:
double length;
double height;
};
// ??? //
int main(){
Rectangle a1, a2, a3, bestA;
River b1, b2, b3, bestB;
std::cin>>a1.length>>a1.height>>a2.length>>a2.height>>a3.length>>a3.height;
std::cin>>b1.name>>b1.length>>b2.name>>b2.length>>b3.name>>b3.length;
bestA=longestOfTheThree<Rectangle>(a1,a2,a3);
bestB=longestOfTheThree<River>(b1,b2,b3);
std::cout<<bestA.length<<" "<<bestA.height<<" "<<bestB.name<<" "<<bestB.length<<"\n";
return 0;
}
2. Class templates
2.1. Class declaration
We can also create a generic class in which one (or more) data types are generalized. These general data types can later be replaced with specific classes or types of objects.
Here is an example of a class called ThreeComponents. It has three private attributes first, second, and third that can be of different general types: A, B, or C.
template<typename A, typename B, typename C>
class ThreeComponents{
private:
A first; B second; C third;
public:
void setFirst(const A &);
void setSecond(const B &);
void setThird(const C &);
A getFirst() const;
B getSecond() const;
C getThird() const;
};
2.2. Object declaration
We have still not implemented the methods. Before we do so, we will show how the objects will be declared. We will assume that the class was declared as above. We will also assume that methods are implemented (they are not, we will implement them later). Once these two assumptions are made, we can use the above general class for several purposes. We can use the class to store data about people. The two components can be first and last name, while the third component is an id number. The class can also be used to keep data about lakes. The first component can be the name, the second component can be the area, and the third component can be the maximal depth. Here is a code that declares one person and one lake.
ThreeComponents<std::string, std::string, long> person;
person.setFirst("John");
person.setSecond("Smith");
person.setThird(12345);
ThreeComponents<std::string,double,double> lake;
lake.setFirst("Michigan");
lake.setSecond(58030);
lake.setThird(281);
2.3. Implementation of methods
A special syntax has to be used when implementing the methods. We must first specify that we are making the methods for the general class. We need to choose the variables for the general data types. In the case of our class ThreeComponents we will pick the same three variables: A, B, and C for general data types. Instead of the name ThreeComponents we must use ThreeComponents<A,B,C>.
The methods setFirst and getFirst are implemented with the code listed below.
template<typename A, typename B, typename C>
void ThreeComponents<A,B,C>::setFirst(const A& x){first=x;}
template<typename A, typename B, typename C>
A ThreeComponents<A,B,C>::getFirst() const{return first;}
2.4. Complete program
We will now implement the entire class ThreeComponents. In addition, we will create a function printComponents that accepts one object of the class ThreeComponents and prints the attributes.
#include<iostream>
template<typename A, typename B, typename C>
class ThreeComponents{
private:
A first; B second; C third;
public:
void setFirst(const A &);
void setSecond(const B &);
void setThird(const C &);
A getFirst() const;
B getSecond() const;
C getThird() const;
};
template<typename A, typename B, typename C>
void ThreeComponents<A,B,C>::setFirst(const A& x){first=x;}
template<typename A, typename B, typename C>
void ThreeComponents<A,B,C>::setSecond(const B& x){second=x;}
template<typename A, typename B, typename C>
void ThreeComponents<A,B,C>::setThird(const C& x){third=x;}
template<typename A, typename B, typename C>
A ThreeComponents<A,B,C>::getFirst() const{return first;}
template<typename A, typename B, typename C>
B ThreeComponents<A,B,C>::getSecond() const{return second;}
template<typename A, typename B, typename C>
C ThreeComponents<A,B,C>::getThird() const{return third;}
template<typename A, typename B, typename C>
void printComponents(const ThreeComponents<A,B,C>& t){
std::cout<<t.getFirst()<<" "<<t.getSecond()<<" "<<t.getThird()<<"\n";
}
int main(){
ThreeComponents<std::string, std::string, long> person;
person.setFirst("John");
person.setSecond("Smith");
person.setThird(12345);
ThreeComponents<std::string,double,double> lake;
lake.setFirst("Michigan");
lake.setSecond(58030);
lake.setThird(281);
printComponents(person);
printComponents(lake);
return 0;
}
3. Typename enforcement
C++ compilers need additional guidance when working with certain iterators. This happens when the iterator has to be declared for a data structure that is built as a template. We will explain this with an example.
std::set<T>. The return value should be of type T.3.1. First attempt that fails
This first attempt will have correct idea, but will return a compiler error. Then we will explain what is going on and provide the solution to the problem.
The idea is to position an iterator to the beginning of the set. The beginning of the set is the smallest element. Then we will increment the iterator and return the obtained object.
#include<iostream>
#include<set>
template<typename T>
T secondSmallest(const std::set<T>& s){
T res;
if(s.size()<2){return res;}
std::set<T>::const_iterator it;
it=s.begin();
++it;
return *it;
}
int main(){
std::set<std::string> names;
names.insert("Bob"); names.insert("Alice"); names.insert("Carl"); names.insert("Danny");
std::cout<<secondSmallest(names)<<"\n";
std::set<long> numbers;
numbers.insert(900); numbers.insert(700); numbers.insert(800); numbers.insert(1000);
std::cout<<secondSmallest(numbers)<<"\n";
return 0;
}
The compiler will return an error. It will look something like:
error: need ‘typename’ before ‘std::set<T>::const_iterator’ because ‘std::set<T>’ is a dependent scope
3.2. Second attempt that succeeds
There is one command that can have too possible meanings. The compiler does not know which of the two meanings the programmer had in mind. The offending line is:
std::set<T>::const_iterator
- First meaning: The programmer wants to declare an object of the type
std::set<T>::const_iterator. This is actually what we wanted to do in the program. - Second meaning: The programmer wants to make an implementation of the method for the class
std::set<T>and the method will be calledconst_iterator.
We want to clarify to the compiler that we want the command std::set<T>::const_iterator to have the first of the possible two meanings. We want to specify that we are using the command to declare an object. In other words, we want the compiler to treat std::set<T>::const_iterator as a data type. This is achieved by placing the word typename before the command.
There is only one word that is needs to be added to make the correct code. The improved program is:
#include<iostream>
#include<set>
template<typename T>
T secondSmallest(const std::set<T>& s){
T res;
if(s.size()<2){return res;}
typename std::set<T>::const_iterator it;
it=s.begin();
++it;
return *it;
}
int main(){
std::set<std::string> names;
names.insert("Bob"); names.insert("Alice"); names.insert("Carl"); names.insert("Danny");
std::cout<<secondSmallest(names)<<"\n";
std::set<long> numbers;
numbers.insert(900); numbers.insert(700); numbers.insert(800); numbers.insert(1000);
std::cout<<secondSmallest(numbers)<<"\n";
return 0;
}
Create a generic function kthLargest that accepts one argument of type set. The function should return the \(k\)-th largest element of the set. You are allowed to assume that the function will only be applied to sets that have at least \(k\) elements.
You should only write the code that replaces the text // ??? //.
#include<iostream>
#include<set>
template<typename T>
T kthLargest(const std::set<T> & a, long k){
// ??? //
}
int main(){
std::set<long> numbers; std::set<std::string> words;
long n; std::string w;
std::cin>>n;
while(n>0){numbers.insert(n);std::cin>>n;}
std::cin>>w;
while(w!="end"){words.insert(w);std::cin>>w;}
long k; std::cin>>k;
std::cout<<kthLargest<long>(numbers,k)<<" "<<kthLargest<std::string>(words,k)<<"\n";
return 0;
}