0

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; } 
6
  • That operator<< is in a .cpp file, and it isn't going to instantiate itself. You instantiate array, so it looks like you know what to do when a templete is defined in a .cpp file. But array instantiations are not doing anything to operator<<. You need explicit instantiations for it as well. Commented May 1, 2024 at 17:39
  • Note to whoever closed that as a dupe of this: that was a mistake. Note explicit instantiations of array in the .cpp file. Un-closing. Note to others who are tempted to repeat the same mistake: please don't. Commented May 1, 2024 at 18:06
  • @n.m.couldbeanAI Thanks, it now works but I get a warning now for each explicit declaration. Commented May 1, 2024 at 19:23
  • @Kominops And the warning is difficult to copy-paste, or why aren't you telling? Just trying to make things more interesting for everyone, I guess? Commented May 1, 2024 at 19:32
  • @ChristianStieber sorry, I placed an answer to my question with the warning in it, that's why I didn't add it as a comment. Commented May 1, 2024 at 20:01

1 Answer 1

0

So thanks to @n.m.couldbeanAI I found that the solution is to place the following 3 lines to my array.cpp

template std::ostream &operator<<(std::ostream &left, const array<int> &right); template std::ostream &operator<<(std::ostream &left, const array<float> &right); template std::ostream &operator<<(std::ostream &left, const array<double> &right); 

It works, but I get one warning for each (i get two other similars):

warning: friend declaration ‘template<class U> std::ostream& operator<<(std::ostream&, const array<U>&’ is not visible to explicit specialization 10 | template std::ostream &operator<<(std::ostream &left, const array<int> &right); ^~~~~~~~` `In file included from array.cpp:2: array.h:22:52: note: friend declaration here 22 | template <typename U> friend std::ostream &operator<<(std::ostream &left, const array<U> &right); 

Edit: Thanks to @ChristianStieber I moved the 3 lines at the end of the file and the code compiles without warnings.

Sign up to request clarification or add additional context in comments.

1 Comment

Move the new instantiations to the end of the file. Why? No idea :-) I'm actually surprised they work as forward declarations at all, but that's probably just me.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.