Generics/Templates

Hello,

I find myself with a situation that has Generics written all over it, but I've never (successfully) implemented them. I understand the theory, but the examples never seem even closely related to what I want to do.

In this case, I have a very basic function requirement: print all values of a table. I'm using different types of containers, mostly vectors of ints, floats or booleans. The tables are a vector of these vectors. Thus, for example, the int table would be: vector<vector<int>>, or using typedef: "intlist = vector<int>" & "intbox = vector<intlist>".

Currently, each primitive variable type I need has its own list and box container. However, this also means that basic functions that take a table as input need to be redefined for each of the primitive types. As a result, every primitive type's list and box containers have a print function associated with them, which does the exact same for every primitive type, but takes a different parameter (e.g. one for intbox, one for floatbox, etc). The actual code is the exact same, except for the datatype in the parameter.

Could anyone show me how to use generics/templates for this?

Thanks in advance!
Can you please show me the code for one primitive so I get a better grasp on what you are trying to do?
Sure thing! This is the example for a table of ints:

typedef vector<int> intlist;
typedef vector<intlist> intbox;

void printIntlist(intlist &list) {
for (int i1 = 0; i1 < list.size(); ++i1) {
cout << list[i1] << " ";
}
}

void printIntbox(intbox &box) {
for (int i1 = 0; i1 < box.size(); ++i1) {
printIntlist(box[i1]);
cout << endl;
}
}

The code for floats etc. looks the exact same, except for the parameter type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

template<class collection>
void printBox(const collection &box)
{
	for (collection::const_iterator i=box.begin(); i!=box.end(); ++i) {
		printList(*i);
		cout << endl;
	}
}

template<class collection>
void printList(const collection &elemList)
{
	for (collection::const_iterator i=elemList.begin(); i!=elemList.end(); ++i) {
		cout << *i << " ";
	}
	return;
}


The two functions printList and printBox will work with any STL collection type (well, printBox only with collections of collections of course). If you want to use maps (you probably won't, but imagine you do) you will need another function:

1
2
3
4
5
template<typename left, typename right>
ostream& operator<<(ostream &ost, const pair<left, right>& arg)
{
	return ost<<arg.first<< ":"<< arg.second;
}


-- you will need to overload the << operator like that for ANY non standard type you want to print btw.

Sample code included (wow, I am so generous today):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>

using namespace std;

template<typename left, typename right>
ostream& operator<<(ostream &ost, const pair<left, right>& arg)
{
	return ost<<arg.first<< ":"<< arg.second;
}

template<class collection>
void printBox(const collection &box)
{
	for (collection::const_iterator i=box.begin(); i!=box.end(); ++i) {
		printList(*i);
		cout << endl;
	}
}

template<class collection>
void printList(const collection &elemList)
{
	for (collection::const_iterator i=elemList.begin(); i!=elemList.end(); ++i) {
		cout << *i << " ";
	}
	return;
}
int main()
{
   list<int> intList;
   vector<list<int> > intBox;
   for(int i=0; i<10;++i)
   {
	   intList.push_back(i);
   }
   for(int i=0; i<10;++i)
   {
	   intBox.push_back(intList);
   }
   printBox(intBox);
   map<int,string> mp;
   mp[5] = "Hello";
   mp[3] = "Bye";
   mp[2] = "Lol";
   printList(mp);
   cin.ignore(cin.rdbuf()->in_avail()+1);
 }


EDIT: Made the pair a const pair reference in the << operator. Seriously, what was I thinking?
EDIT2: Added an extra space in the main function, to prevent compiler collapse (some compilers might read vector<list<int>> intBox; as operator>>(vector<list<, intintBox); instead of vector<list<int> > intBox; Stupid I know, but it could happen.
Last edited on
Thanks a ton! Will try it out later tonight.
Small additional question: What's the gain in adding "const" to parameter passed by reference?
That you can pass a constant parameter. Printing a constant doesn't change the constant, so it should be allowed. Otherwise you could only use mutable datatypes, which is pointless.

PS: Depending on your situation, it may be more meaningful to additionally pass an ostream& to your function, so you could choose where to print, for example like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<class collection>
void printBox(const collection &box, ostream& out=cout)
{
	for (collection::const_iterator i=box.begin(); i!=box.end(); ++i) {
		printList(*i, out);
		out << endl;
	}
}

template<class collection>
void printList(const collection &elemList, ostream& out=cout)
{
	for (collection::const_iterator i=elemList.begin(); i!=elemList.end(); ++i) {
		out << *i << " ";
	}
	return;
}
Last edited on
Works like a charm!

I'm using the direct-print version, since that's all I need for the current program. I'm wondering what the "out=cout" notation means though? (In your return-to-ostream version)

Also, why do you still add "return" to void methods? Is it just a habit, or is it proper/better coding practice?
Last edited on
Also, why do you still add "return" to void methods? Is it just a habit, or is it proper/better coding practice?


For consistency issues. If all the other functions have return statements, why shouldn't the void functions have one? It's technically not needed, but I personally like it better that way.

I'm wondering what the "out=cout" notation means though?


It's a default parameter. Basically, what it says is that you can provide an ostream as a third parameter, however you do not have to. If you don't, the function behaves as if you passed cout as the third parameter. Which means, you could replace the second version of those templates by the first without changing the code at all, but it would gave you the power to call the function e.g. with an ofstream as the third parameter, which would print the whole thing to a file instead.
Last edited on
Ah of course, I've used that before, but just with a default numerical value. Never seen it used like that. Quite ingenious!

Once again, thank you for all your help! You've made my life so much easier!
Topic archived. No new replies allowed.