Search code examples
c++templatessyntaxcompiler-errorsfriend-function

How do I declare a template friend function that is type-specific in my template class?


I've recently learned that there are two ways to declare a template friend class or function. For example, to declare a template friend class, you may do this

template <typename T>
class goo
{
    template <typename T>
    friend class foo;
};

or this

template <typename T>
class goo
{
    friend class foo <T>;
};

These two declarations are in fact different. The former allows you to use any type of template friend class foo with any type of template friend class goo. Where as the latter only allows you to use the same type such that you may do foo<int> with goo<int> but not foo<int> with goo<char>.

In the header file below, I try to use the latter form of the declaration to make my template friend function friend std::ostream& operator<<(std::ostream&, const Array<T>&); more type-specific in an effort to make my program more encapsulated.

//ARRAY_H

#include <iostream>
#include "Animal.h"

const int DefaultSize = 3;

template <typename T> // declare the template and the paramenter
class Array               // the class being parameterized
{
public:
  Array(int itsSize = DefaultSize);
  Array(const Array &rhs);
  ~Array() { delete[] pType; }

  // operators
  Array& operator=(const Array&);
  T& operator[](int offSet) { return pType[offSet]; }
  const T& operator[](int offSet) const { return pType[offSet]; }

  // accessors
  int GetSize() const { return itsSize; }

  // friend function
  friend std::ostream& operator<< <T>(std::ostream&, const Array<T>&);

private:
  T *pType;
  int itsSize;
};

template <typename T>
Array<T>::Array(int size = DefaultSize) :itsSize(size)
{
  pType = new T[size];
  for (int i = 0; i < size; i++)
      pType[i] = static_cast<T>(0);
}

Array<Animal>::Array(int AnimalArraySize) :itsSize(AnimalArraySize)
{
  pType = new Animal[AnimalArraySize];
}

template <typename T>
Array<T>::Array(const Array &rhs)
{
  itsSize = rhs.GetSzie();
  pType = new T[itsSize];
  for (int i = 0; i < itsSize; i++)
      pType[i] = rhs[i];
}

template <typename T>
Array<T>& Array<T>::operator=(const Array &rhs)
{
  if (this == &rhs)
      return *this;
  delete[] pType;
  itsSize = rhs.GetSize();
  pType = new T[itsSize];
  for (int i = 0; i < itsSize; i++)
      pType[i] = rhs[i];
  return *this;
}
template <typename T>
std::ostream& operator<<(std::ostream& output, const Array<T> &theArray)
{
  for (int i = 0; i < theArray.GetSize(); i++)
      output << "[" << i << "]" << theArray[i] << std::endl;
  return output;
}

#endif

However, I receive a compiler error "error C2143: syntax error : missing ';' before '<'" for line 23 which is friend std::ostream& operator<< <T>(std::ostream&, const Array<T>&);.

When using the former form of the declaration by changing line 23 to this

template <typename T>
friend std::ostream& operator<<(std::ostream&, const Array<T>&);

My program executes without any errors.

I assume I cannot use the same syntax from type-specific template friend classes for type-specific template friend functions, or that I may be missing some kind of forward declaration. I've searched through stack-overflow and the closest topic I could find for this problem is here, but they only discuss type-specific template friend classes. I am unable to find a topic that discusses the correct syntax for using a template friend function in this way.

If this is a syntax error, what is the correct way to declare my type-specific template friend function? If this is not a syntax error, why will my program not compile?

Here are the rest of my project files for your reference. The desired behavior of my program is to show how a parametrized array uses a template to create multiple instances of different array types.

//ANIMAL_H

#ifndef ANIMAL_H
#define ANIMAL_H

#include <iostream>

class Animal
{
public:
  // constructors
  Animal();
  Animal(int);
  ~Animal();

  // accessors
  int GetWeight() const { return itsWeight; }
  void SetWeight(int theWeight) { itsWeight = theWeight; }

  // friend operators
  friend std::ostream& operator<<(std::ostream&, const Animal&);

private:
  int itsWeight;
};

#endif

//ANIMAL.CPP

#include "Animal.h"
#include <iostream>

Animal::Animal() :itsWeight(0)
{
  std::cout << "animal() ";
}

Animal::Animal(int weight) : itsWeight(weight)
{
  std::cout << "animal(int) ";
}

Animal::~Animal()
{
  std::cout << "Destroyed an animal...";
}

std::ostream& operator<<(std::ostream& theStream, const Animal& theAnimal)
{
  theStream << theAnimal.GetWeight();
  return theStream;
}

//MAIN.CPP

#include <iostream>
#include "Animal.h"
#include "Array.h"

void IntFillFunction(Array<int>& theArray);
void AnimalFillFunction(Array<Animal>& theArray);

int main()
{
  Array<int> intArray;
  Array<Animal> animalArray;
  IntFillFunction(intArray);
  AnimalFillFunction(animalArray);
  std::cout << "intArray...\n" << intArray;
  std::cout << "\nanimalArray...\n" << animalArray << std::endl;

  std::cin.get();

  return 0;
}

void IntFillFunction(Array<int>& theArray)
{
  bool Stop = false;
  int offset, value;
  while (!Stop)
  {
      std::cout << "Enter an offset (0-9) and a value. ";
      std::cout << "(-1 to stop): ";
      std::cin >> offset >> value;
      if (offset < 0)
          break;
      if (offset > 9)
      {
          std::cout << "***Please use values between 0 and 9.***\n";
          continue;
      }
      theArray[offset] = value;
  }
}

void AnimalFillFunction(Array<Animal>& theArray)
{
  Animal *pAnimal;
  for (int i = 0; i < theArray.GetSize(); i++)
  {
      pAnimal = new Animal(i * 10);
      theArray[i] = *pAnimal;
      delete pAnimal;
  }
}

Solution

  • You need a forward declaration of the function template (just as you need with a class template) in order to friend a specialization. Your code should be:

    template <typename T>
    std::ostream& operator<<(std::ostream& output, const Array<T> &theArray);
    
    template <typename T>
    class Animal
    {
        // ...
        friend std::ostream& operator<< <T>(std::ostream&, const Array<T>&);
    };