13.6.2.2. Tree Example 2: Two Template Variables

This example program is similar to the previous example but demonstrates a container class based on two template variables. This version of the tree tree class, named KVTree implements a map container that associates a key with a value (a KV pair). Maps do not allow duplicate keys and a key can map to only one value. The demonstration consists of three files:

  1. KVTree.h: demonstrates how to create a general data structure based on two template variables (as in the previous example, the template class and all functions must be in a single header file)
  2. Employe.h: a simple data class that demonstrates the KVTree class. The program instantiates Employee objects and stores them in a KVTree
  3. Employee.cpp: is a simple driver that defines and uses a KVTree to store instances of Employee - the program demonstrates how to create an instance of a template class that requires the programmer to specify two template classes
#include <iostream>
using namespace std;


template <class K, class V>
class KVTree
{
	private:
		K	key;					// 1
		V	value;					// 2
		KVTree*	left = nullptr;				// 3
		KVTree*	right = nullptr;

	public:
		//KVTree() : left(nullptr), right(nullptr) {}	// 4
		~KVTree();
		V* insert(K& a_key, V& a_value);
		V* search(K& a_key);
		void list() { right->_list(); }			// 5

	private:
		void	_list();				// 6
};
KVThree.h: Key-Value Tree class specification. A binary tree that maps key (template type K)to a value (template type V). The insert and search operations look for the key object but return a pointer to the corresponding value object. Key-value data structures are sometimes called associative data structures because they associate a key with a value.
  1. A key object used for inserting and searching.
  2. A value object associated with the key.
  3. Member assignment in a class specification requires an ANSI 2014 compliant compiler or newer.
  4. Initializing data members with a constructor.
  5. The public interface for the list operation calls the private _list function, initiating the descent on the right sub-tree.
  6. The private _list function does most of the work of the list operation.
template <class K, class V>
KVTree<K, V>::~KVTree()
{
	if (left != nullptr)
		delete left;
	if (right != nullptr)
		delete right;
}
KVThree.h: destructor. The destructor is a recursive function: delete left and delete right each trigger a recursive call to the destructor function.
template <class K, class V>
V* KVTree<K, V>::insert(K& key, V& value)
{
	KVTree* root = this;				// 1
	KVTree* down = right;				// 2

	while (down != nullptr && down->key != key)	// 3
	{
		root = down;
		if (key < down->key)
			down = down->left;
		else
			down = down->right;
	}

	if (down != nullptr)				// 4
		return &down->value;

	down = new KVTree;				// 5
	down->key = key;
	down->value = value;				// 6
	if (key < down->key)
		root->left = down;
	else
		root->right = down;

	return &down->value;
}
KVThree.h: the insert function. This implementation of a binary tree has three important characteristics: This implementation requires that operator< and operator== are implemented for the key class that replaces the template variable K.
  1. root and down are used to locate the insertion point
  2. Loops until (a) the bottom of a sub-tree is located or (b) a previously inserted object with the same key information is found. It is more common to implement operator== than it is is to implement operator!=, so the equality operator is used with the negation operator ( ! )
  3. Returns a pointer to the previously inserted value if a corresponding key is found
  4. Creates a new tree node and installs the value if the corresponding key is not found in the tree
  5. Binary trees are ordered. A typical ordering is to insert a new key/value pair in the left sub-tree if it is less than the key in the root of the current sub-tree; other wise it is inserted in the right sub-tree
  6. Returns a pointer to the newly inserted data
template <class K, class V>
V* KVTree<K, V>::search(K& key)
{
	KVTree* root = right;		// 1

	while (root != nullptr && root->key != key)	// 2
	{
		if (key < root->key)
			root = root->left;
		else
			root = root->right;
	}

	if (root == nullptr)		// 2
		return nullptr;
	else
		return &root->value;
}
KVThree.h: the search function. The search operation returns a pointer to the value if the corresponding key is found, otherwise it returns nullptr.
  1. root is used to descend the tree while looking for the tree node that stores the searched for key
  2. Loops until (a) the bottom of the tree is located or (b) the searched for key is located.
  3. Returns a pointer to the value associated with the search key or nullptr if the key is not found.
template <class K, class V>
void KVTree<K, V>::_list()
{
	if (left != nullptr)
		left->_list();
	cout << value << endl;
	if (right != nullptr)
		right->_list();
}
Three.h: the private _list function. The _list function recursively descends the tree printing the both the key and the value in-order (pre-order and post-order are achieved by moving the cout statement to the top or bottom of the function respectively). The _list function assumes that operator<< is overloaded for both template classes that replaces K and V respectively. A more complete or authentic implementation would probably define two functions: one to list the keys and one to list the values.
#include <iostream>
#include <string>
using namespace std;

class Employee
{
	private:
		string	name;
		int	id;

	public:
		Employee(string n = "", int en = 0) : name(n), id(en) {}

		friend ostream& operator<<(ostream& out, Employee& me)
		{
			out << me.name << " " << me.id;
			return out;
		}
};
Employee.h: Employee class specification. The KVTree list function requires that instances of the value class define operator<<.
#include <iostream>
#include <string>
#include "KVTree.h"
#include "Employee.h"
using namespace std;

int main()
{
	KVTree<string, Employee> tree;
	string			 name;					// Key

	while (true)
	{
		cout << "N\tEnter a new Employee" << endl;
		cout << "S\tSearch for an Employee" << endl;
		cout << "L\tList all Employees" << endl;
		cout << "E\tExit" << endl;

		cout << "Operation: ";
		char operation;
		cin >> operation;
		cin.ignore();

		switch (operation)
		{
			case 'N':
			case 'n':
			{
				cout << "Employee name: ";
				getline(cin, name);
				int	number;
				cout << "Employee Number: ";
				cin >> number;
				cin.ignore();
				Employee e(name, number);		// Value
				tree.insert(name, e);
				break;
			}

			case 'S':
			case 's':
			{
				cout << "Employee name: ";
				getline(cin, name);
				Employee* e = tree.search(name);
				if (e != nullptr)
					cout << "Employee: " << *e << endl;
				else
					cout << name << " NOT FOUND" << endl;
				break;
			}

			case 'L':
			case 'l':
				tree.list();
				break;

			case 'E':
			case 'e':
				return 0;
				//exit(0);	// ends the program but doesn't call the destructor

			default:
				cerr << "Unrecognized operation: \"" << operation << "\"" << endl;
				break;
		}
	}

	return 0;
}
Employee.cpp. A client program that uses the KVTree template class. The highlighted statement demonstrates how to define an instance of the KVTree class and how to specify the classes (string and Employee) stored in the KVTree. The class name string replaces the template variable K while the class name Employee replaces the template variable V when the program is compiled.

Downloadable Code