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;
}
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.