C++ template syntax

This post will cover some less common syntax that is useful when dealing with templates. The class used to demonstrate the syntax is only as an example, it is not supposed to be a good example of a working smart pointer class.

Let's start with a simple SmartPointer class and how to write template function definitions outside of the class definition:

// define the class
//
template<typename PointerType>
class SmartPointer
{
public:
  SmartPointer(PointerType *pointer);
  ~SmartPointer();

private:
  PointerType *m_pointer;
};

// define the member functions
//
template<typename PointerType>
inline SmartPointer<PointerType>::SmartPointer(PointerType *pointer)
: m_pointer(pointer)
{
  // do some stuff
}

template<typename PointerType>
inline SmartPointer<PointerType>::~SmartPointer(PointerType *pointer)
{
  // do some stuff
}

As you can see this is pretty similar to defining a normal member function definition outside a class. The only extra things you need are the list of template parameters template<typename PointerType> before the member function and then the class template parameters between the class name and the scope resolution operator SmartPointer<PointerType>::.

So pretty simple so far, now let's try adding a templated member function to our templated class. Member functions that have already been defined previously will be left out of later examples.

// define the class
//
template<typename PointerType>
class SmartPointer
{
public:
  SmartPointer(PointerType *pointer);
  ~SmartPointer();

  template<typename OtherPointerType>
  bool operator == (const SmartPointer<OtherPointerType> &rhs) const;
private:
  PointerType *m_pointer;
};

// define the member functions
//
template<typename PointerType>
template<typename OtherPointerType>
bool SmartPointer<PointerType>::operator == (
  const SmartPointer<OtherPointerType> &rhs) const
{
  // this is only valid for the same type or derived types and won't compile for
  // unrelated types.
  //
  return m_pointer == rhs.m_pointer;
}

So we have overloaded the equality operator to take a SmartPointer type with a different template type OtherPointerType. This lets you compare two SmartPointers without the pointers sharing a type. As you can see defining the template function outside of the class requires the additional declaration of the list of function template parameters template<typename OtherPointerType> after the list of class template parameters template<typename PointerType> on line 18. The template argument also requires the template parameter <OtherPointerType> to indicate it is of a different type to the class.

Next up we'll try adding a global operator overload that is also a friend of the class. This function will let us compare the SmartPointer to a raw pointer value without having to worry about which side of the operator the class is on.

// define the class
//
template<typename PointerType>
class SmartPointer
{
public:
  SmartPointer(PointerType *pointer);
  ~SmartPointer();

  template<typename OtherPointerType>
  bool operator == (const SmartPointer<OtherPointerType> &rhs) const;
private:
  PointerType *m_pointer;
};

// define the member functions
//
template<typename PointerType>
template<typename OtherPointerType>
bool SmartPointer<PointerType>::operator == (
  const SmartPointer<OtherPointerType> &rhs) const
{
  // this is only valid for the same type or derived types and won't compile for
  // unrelated types.
  //
  return m_pointer == rhs.m_pointer;
}

This example is not any trickier than before but it does require some forward declaration which the others did not. The need for the forward declarations stems from the fact that the global operator == overload function must be a friend of the SmartPointer class so it can access the private member SmartPointer::m_pointer.
For the friend declaration function to compile without complaint the global operator == function must be declared before the class is defined.
For the global operator == function declaration to compile without complaint the SmartPointer class must be defined.
So for everything to work we must forward declare the SmartPointer, then the global operator == function and then we can define the class with the additional friend function declaration.
Another thing to note is the scope resolution :: within the friend function declaration, this lets the class know that the function belongs to the global scope, especially useful if the class belongs to a namespace.

As a final step let's see what the last snippet would look like with the SmartPointer class belonging to a namespace lib:

// this forward declaration is required by the global operator == function
//
template<typename PointerType> class SmartPointer;

// this forward declaration is required for the friend declaration inside the
// SmartPointer class.
//
template<typename PointerType>
inline bool operator == (const void *lhs, const SmartPointer<PointerType> &rhs);

// define the class
//
template<typename PointerType>
class SmartPointer
{
  // let the class know the global function is a friend
  //
  template<typename OtherPointerType>
  friend bool ::operator == (const void *lhs, const SmartPointer<OtherPointerType> &rhs);

public:
  SmartPointer(PointerType *pointer);
  ~SmartPointer();

  bool operator == (const void *pointer) const;

  template<typename OtherPointerType>
  bool operator == (const SmartPointer<OtherPointerType> &rhs) const;
private:
  PointerType *m_pointer;
};

// define the member functions
//
template<typename PointerType>
inline bool SmartPointer<PointerType>::operator == (const void *rhs) const
{
  return m_pointer == rhs;
}

// define the global functions
//
template<typename PointerType>
inline bool operator == (const void *lhs, const SmartPointer<PointerType> &rhs)
{
  return lhs == rhs.m_pointer;
}

There's not really much change from the last example just the addition of the namespace and scope resolution lib:: to the global operator == function SmartPointer argument and the namespace lib scope around the class and around the member function definition. Also worth noting that there is no need to close and reopen the namespace between the class definition and member function definition but if they were in separate files (.h header and .inl inline) it might be useful.

Hopefully, if you didn't already you now know some of the less common syntax used when dealing with templates.