friend Declarations in Class Templates
You can have three kinds of friend declarations within a class template:
- A non-template friend class or friend function
- A bound friend class template or function template.
- An unbound friend class template or function template
A non-template friend class or friend function
template <typename T>
class QueueItem
{
friend class Mumble;
friend int bumble(double d);
friend char *Fumble::rumble();
// …
};
In this example, class Mumble, function bumble(), and member function rumble() in class Fumble are friends to all instantiations of the class template QueueItem.
A bound friend class template or friend function
template <typename T>
class QueueItem
{
friend class Mumble<T>;
friend int bumble<T>( QueueItem<T> );
friend char *Fumble<T>::rumble();
// …
};
In this example, the friends are all template instantiations based on the same type, T, as the enclosing class template, QueueItem<T>.
A 1-to-1 mapping is defined between the instantiation of the class template QueueItem<T> and the template instantiations of the friends.
An unbound friend class template or friend function
template <typename T>
class QueueItem
{
template <typename U>
friend class Mumble;
template <typename V>
friend int bumble( QueueItem<V> );
template <typename W>
friend char *Fumble<W>::rumble();
// …
};
Here, the friends are all class templates based on types different from the type, T, of the enclosing class template, QueueItem<T>.
A 1-to-many mapping is defined between the instantiation of the class template QueueItem<T> and the template instantiations of the friends. For each type instantiation of QueueItem, all instantiations of Mumble, bumble() and Fumble<W>::rumble() are friends.
Using Friends in our Queue Class
Let’s make some changes to our Queue class.
Note that the QueueItem class in our Queue example is not intended for use outside the Queue class.
We can enforce that by making its constructor private and specifying that the Queue class is a friend.
Nothing else needs to change. (The main function is the same.)
//
// Queue.h
// Queue Template Class with friends
//
// Created by Bryan Higgs on 10/17/24.
//
#ifndef Queue_h
#define Queue_h
#include "assert.h"
// Forward declaration
template <typename TYPE>
class QueueItem;
// The Queue
template <typename TYPE>
class Queue
{
public:
typedef void func_t(QueueItem<TYPE> *qiPtr);
Queue()
: m_front(0), m_back(0)
{}
~Queue()
{
while (!IsEmpty())
RemoveFromFront();
}
void AddToBack(const TYPE &value)
{
QueueItem<TYPE> *qiPtr
= new QueueItem<TYPE>(value);
if (IsEmpty())
m_front = m_back = qiPtr;
else
{
m_back->setNext(qiPtr);
m_back = qiPtr;
}
}
const TYPE RemoveFromFront()
{
if(IsEmpty())
return 0; // Nothing to remove
QueueItem<TYPE> *qiPtr = m_front;
m_front = m_front->getNext();
const TYPE theValue = qiPtr->getValue();
delete qiPtr; // Deallocate the QueueItem
return theValue;
}
bool IsEmpty() const
{
return m_front == 0;
}
void ForEachItem(func_t *func)
{
for (QueueItem<TYPE> *qi = m_front;
qi != 0; qi = qi->getNext())
{
func(qi);
}
}
private:
QueueItem<TYPE> *m_front;
QueueItem<TYPE> *m_back;
};
// A Queue item
template <typename TYPE>
class QueueItem
{
friend class Queue<TYPE>; // Make friends
public:
const TYPE getValue() const
{ return m_item; }
QueueItem* getNext()
{ return m_next; }
void setNext(QueueItem* next)
{ m_next = next; }
private:
QueueItem(const TYPE &item)
: m_item(item), m_next(0)
{}
const TYPE m_item;
QueueItem *m_next;
};
#endif /* Queue_h *///
// main.cpp
// Queue Template Class
//
// Created by Bryan Higgs on 10/17/24.
//
#include <iostream>
#include "Queue.h"
void displayItem( QueueItem<int> *qiPtr )
{
std::cout << "QueueItem: " << qiPtr
<< " value = "
<< qiPtr->getValue() << std::endl;
}
int main(int argc, const char * argv[])
{
Queue<int> queue;
for (int i = 0; i < 5; i++)
{
queue.AddToBack(i*i);
// The square of i
}
queue.ForEachItem(displayItem);
for (int i = 0; i < 5; i++)
{
const int value
= queue.RemoveFromFront();
std::cout << "[" << i << "] : "
<< value << std::endl;
}
return 0;
}
//
// main.cpp
// Template Nontype Parameters
//
// Created by Bryan Higgs on 10/17/24.
//
#include <iostream>
#include "CharArray.h"
int main(int argc, const char * argv[])
{
CharArray<8> carray1;
for (int i = 0; i < carray1.getSize(); i++)
{
carray1[i] = 'a' + i;
}
std::cout << "Array has " << carray1.getSize()
<< " elements" << std::endl;
for (int i = 0; i < carray1.getSize(); i++)
{
std::cout << "[" << i << "] : "
<< carray1[i] << std::endl;
}
return 0;
}which outputs:
QueueItem: 0x600000008020 value = 0
QueueItem: 0x600000008030 value = 1
QueueItem: 0x600000008040 value = 4
QueueItem: 0x600000008050 value = 9
QueueItem: 0x600000008060 value = 16
[0] : 0
[1] : 1
[2] : 4
[3] : 9
[4] : 16
Program ended with exit code: 0
Nested Types in Class Templates
Even better: Make QueueItem a nested private type within the Queue class:
//
// Queue.h
// Queue Template Class with Nesting
//
// Created by Bryan Higgs on 10/17/24.
//
#ifndef Queue_h
#define Queue_h
// The Queue
template <typename TYPE>
class Queue
{
public:
typedef void func_t(TYPE value); // Note change of signature
Queue()
: m_front(0), m_back(0)
{}
~Queue()
{
while (!IsEmpty())
RemoveFromFront();
}
void AddToBack(const TYPE &value)
{
QueueItem *qiPtr
= new QueueItem(value); // Note removal of <TYPE>
if (IsEmpty())
m_front = m_back = qiPtr;
else
{
m_back->setNext(qiPtr);
m_back = qiPtr;
}
}
const TYPE RemoveFromFront()
{
if(IsEmpty())
return 0; // Nothing to remove
QueueItem *qiPtr = m_front; // Note removal of <TYPE>
m_front = m_front->getNext();
const TYPE theValue = qiPtr->getValue();
delete qiPtr; // Deallocate the QueueItem
return theValue;
}
bool IsEmpty() const
{
return m_front == 0;
}
void ForEachItem(func_t *func)
{
for (QueueItem *qi = m_front; // Note removal of <TYPE>
qi != 0; qi = qi->getNext())
{
func(qi->getValue());
}
}
private:
// A Queue item, now private
class QueueItem // Note removal of <TYPE>
{
public:
QueueItem(const TYPE &item)
: m_item(item), m_next(0)
{}
const TYPE getValue() const
{ return m_item; }
QueueItem* getNext()
{ return m_next; }
void setNext(QueueItem* next)
{ m_next = next; }
private:
const TYPE m_item;
QueueItem *m_next;
};
QueueItem *m_front; // Note removal of <TYPE>
QueueItem *m_back; // Note removal of <TYPE>
};
#endif /* Queue_h */We had to change the main program:
//
// main.cpp
// Queue Template Class with Nesting
//
// Created by Bryan Higgs on 10/17/24.
//
#include <iostream>
#include "Queue.h"
void displayItem( int value )
{
std::cout << ' ' << value;
}
int main(int argc, const char * argv[])
{
Queue<int> queue;
for (int i = 0; i < 5; i++)
{
queue.AddToBack(i*i);
// The square of i
}
queue.ForEachItem(displayItem);
std::cout << std::endl;
for (int i = 0; i < 5; i++)
{
const int value
= queue.RemoveFromFront();
std::cout << "[" << i << "] : "
<< value << std::endl;
}
return 0;
}
which outputs:
0 1 4 9 16
[0] : 0
[1] : 1
[2] : 4
[3] : 9
[4] : 16
Program ended with exit code: 0