I wouldn't have this problem if I put the class code in the same header or same .cpp file, but when I have the class specification in the header and the class code in a separate .cpp file, I get this error when compiling main.cpp:
/usr/bin/ld: ex2.o: in function `main': ex2.cpp:(.text+0xd0): undefined reference to `std::ostream& operator<< <int>(std::ostream&, array<int> const&)' /usr/bin/ld: ex2.cpp:(.text+0xe6): undefined reference to `std::ostream& operator<< <float>(std::ostream&, array<float> const&)' /usr/bin/ld: ex2.cpp:(.text+0xfc): undefined reference to `std::ostream& operator<< <double>(std::ostream&, array<double> const&)' collect2: error: ld returned 1 exit status I can't find a way to solve this. I even tried the accepted solution at Undefined reference to template operator with definition in same header file, but it doesn't work, I get other errors.
So, is there a way to solve this? Do I just put the class.cpp code in the header file (or include it)? Below is the code of each file.
array.h
#ifndef ARRAY_H #define ARRAY_H #include <ostream> template <typename T> class array { public: array (int size); array (const array<T> &ob); ~array (); void setElemet (T value, int pos); int getElement (int pos) const; int getSize() const; void arrayInit (); int operator[](int pos); array<T> &operator=(const array<T> &right); array<T> &operator+=(int right); template <typename U> friend std::ostream &operator<<(std::ostream &left, const array<U> &right); private: T *arr; int size; }; #endif array.cpp
#include <iostream> #include "array.h" #include <random> using std::cout, std::endl; template class array<int>; template class array<float>; template class array<double>; template <typename T> array<T>::array(int size) { this -> size = size; if(this -> size <= 0) { if(this -> size == 0) this -> size = 1; else this -> size *= -1; cout << "Array size corrected, current size = " << this -> size << "." << endl; } try { arr = new T[this -> size]; } catch (const std::exception& ex) { cout << "Memory allocation failed, reason: " << ex.what() << endl; std::terminate(); } arrayInit(); } template <typename T> array<T>::array(const array<T> &ob) { this -> size = ob.size; try { arr = new T[this -> size]; } catch (const std::exception& ex) { cout << "Memory allocation failed, reason: " << ex.what() << endl; std::terminate(); } for (int i = 0; i < size; i++) { arr[i] = ob.arr[i]; } } template <typename T> array<T>::~array () { delete[] arr; } template <typename T> void array<T>::setElemet (T value, int pos) { if(pos < 0 || pos >= size) { cout << "Invalid position, array ranges from 0 to " << size - 1 << "." << endl; } else { arr[pos] = value; } } template <typename T> int array<T>::getElement (int pos) const{ if(pos < 0 || pos >= size) { cout << "Invalid position, array ranges from 0 to " << size - 1 << "." << endl; return -1; } else { return arr[pos]; } } template <typename T> int array<T>::getSize() const { return size; } template <typename T> void array<T>::arrayInit (){ std::random_device rd; if(std::is_same<T, int>::value) { std::uniform_int_distribution <int> dist (-10000, 10000); for (int i = 0; i < size; i++) { arr[i] = dist(rd); } } else if (std::is_same<T, float>::value) { std::uniform_real_distribution <float> dist (-10000, 10000); for (int i = 0; i < size; i++) { arr[i] = dist(rd); } } else if (std::is_same<T, double>::value) { std::uniform_real_distribution <double> dist (-10000, 10000); for (int i = 0; i < size; i++) { arr[i] = dist(rd); } } } template <typename T> int array<T>::operator[](int pos) { try { if (pos < 0 || pos >= size) throw pos; return arr[pos]; } catch (int ex) { cout << "Tried to input out of array size. Array ranges from 0 to " << size - 1 << ". Position tried to input: "; return pos; } } template <typename T> array<T> &array<T>::operator=(const array<T> &right) { if (this == &right) return *this; for (int i = 0; i < size; i++) { arr[i] = right.arr[i]; } return *this; } template <typename T> array<T> &array<T>::operator+=(int right) { if (right <= 0) { cout << "Can't expand array with negative or zero input." << endl; return *this; } int oldSize = size; array<T> temp(oldSize); size += right; temp = *this; delete [] arr; try { arr = new T[size]; } catch(const std::exception& ex) { cout << "Memory allocation failed, reason: " << ex.what() << endl; std::terminate(); } for (int i = 0; i < oldSize; i++) { arr[i] = temp[i]; } std::random_device rd; if(std::is_same<T, int>::value) { std::uniform_int_distribution <int> dist (-10000, 10000); for (int i = 0; i < size; i++) { arr[i] = dist(rd); } } else if (std::is_same<T, float>::value) { std::uniform_real_distribution <float> dist (-10000, 10000); for (int i = 0; i < size; i++) { arr[i] = dist(rd); } } else if (std::is_same<T, double>::value) { std::uniform_real_distribution <double> dist (-10000, 10000); for (int i = 0; i < size; i++) { arr[i] = dist(rd); } } return *this; } template <typename T> std::ostream &operator<<(std::ostream &left, const array<T> &right) { if(right.size <= 0) { left << "Array size invalid." << "endl"; } else { for(int i = 0; i < right.size; i++) { left << "Array element " << i << " has a value = " << right.arr[i] << "." << endl; } } return left << endl; } ex2.cpp
#include <iostream> #include "array.h" using std::cout, std::cin, std::endl; int main() { array<int> arr1(3); array<float> arr2(3); array<double> arr3(3); cout << arr1; cout << arr2; cout << arr3; return 0; }
operator<<is in a .cpp file, and it isn't going to instantiate itself. You instantiatearray, so it looks like you know what to do when a templete is defined in a .cpp file. Butarrayinstantiations are not doing anything tooperator<<. You need explicit instantiations for it as well.arrayin the .cpp file. Un-closing. Note to others who are tempted to repeat the same mistake: please don't.