Перегрузка операторов >> и << в шаблонном классе как дружественных функций

1,00
р.
const size_t SIZE = 4
Вот мой класс:
template class Matrix { private: T arr[SIZE][SIZE] friend ostream& operator << (ostream &, const Matrix<T> &) friend istream& operator >> (istream &, Matrix &) void initialize() // функция которая заполняет матрицу как единичную
public: Matrix() Matrix(const Matrix&) const Matrix& operator=(const Matrix&) const Matrix& operator*(const Matrix&) void operator*=(const Matrix &) T* operator[](int row) }
Тут реализованы все остальные работающие операции.
И тут ошибка линковки в реализации этих двух операций:
template< typename T> ostream & operator<<(ostream & os, const Matrix<T> & rhs) { for (int i(0) i < SIZE ++i) { for (int j(0) j < SIZE ++j) { os << rhs.arr[i][j] << ' ' } os << endl } return os }<br>template< typename T> istream & operator>>(istream& is, Matrix & rhs) { for (int i(0) i < SIZE ++i) { for (int j(0) j < SIZE ++j) { is >> rhs.arr[i][j] } } return is }
С чем связано возникновение ошибки?

Ответ
Почему код не работает
Делая следующие объявления:
friend ostream& operator << (ostream &, const Matrix<T> &) friend istream& operator >> (istream &, Matrix &)
Вы говорите компилятору, где-то есть функции, которые принимают потоки первым аргументом и Matrix вторым. Но Matrix здесь не является шаблоном, вопреки тому, что может показаться — шаблоном является класс Matrix, а Matrix здесь является вполне конкретной реализацией, которая будет известна в момент инстанциации шаблона. Более того, никакого кода вообще не существует, пока шаблон не был инстанциирован.
Исходя из этого, наши friend-декларации только тогда вступают в силу, когда происходит инстанциация шаблона. Давайте, к примеру, создадим Matrix. Это даёт нам следующий код, сгенерированный для нашего шаблона:
friend ostream& operator << (ostream &, const Matrix<int> &) friend istream& operator >> (istream &, Matrix &)
Т.е. как Вы можете видеть, у нас есть две нешаблонных friend-декларации. С другой стороны у нас есть реализация шаблонного оператора(приведу только один для краткости):
template< typename T> std::ostream & operator<<(std::ostream & os, const Matrix<T> & rhs) { ... }
Если Вам кажется, что объявление в классе и определение вне его должны быть как-то связаны, то это не так. Это две совершенно разные функции(одна шаблонная, а другая нет), а т.к., согласно ADL(argument-dependent lookup), при вызове оператора, поиск надлежащей функции будет проведён в классе, то компилятор находит нашу friend-декларацию:
friend ostream& operator << (ostream &, const Matrix<int> &)
Но вот реализации этой декларации нет, отсюда и ошибка компоновщика. В лоб, для типа int, это можно было бы исправить следующий образом, надо исправить нашу реализацию на следующую:
std::ostream & operator<<(std::ostream & os, const Matrix<int> & rhs) { ... }
Теперь наш оператор перестал быть шаблоном и поэтому он является реализацией нашей friend-декларации, для шаблона инстанциированного для int.
Как починить
Разумеется, нам это не подходит — не будем же мы писать реализацию под каждый тип инстанциации. Поэтому нам нужно так написать friend-декларацию, чтобы она всегда учитывала тип T. Как это сделать? Нужно завести шаблонный оператор, как Вы и делали раньше, т.е. оставляем старую реализацию:
template< typename T> std::ostream& operator<<(std::ostream & os, const Matrix<T> & rhs) { ... }
Но теперь, в классе Matrix, мы добавим в друзья специализацию этого шаблона:
friend ostream& operator <<<T>(ostream & os, const Matrix & rhs) friend istream& operator >>(istream &, Matrix &)
А так как мы объявляем другом специализацию, то мы должны вынести определение(или только объявление) общего шаблона до класса Matrix, т.е. весь Ваш код будет выглядеть так:
template class Matrix
template< typename T> std::ostream& operator<<(std::ostream & os, const Matrix<T> & rhs) { ... }
template< typename T> std::istream & operator>>(std::istream& is, Matrix & rhs) { ... }
template class Matrix { private: T arr[SIZE][SIZE] friend ostream& operator <<<T>(ostream &, const Matrix &) friend istream& operator >>(istream &, Matrix &) ... }

Ещё один правильный вариант, с минимумом изменений, может быть такой:
friend ostream& ::operator << (ostream &, const Matrix<T> &) friend istream& ::operator >> (istream &, Matrix &)
Но он работает только в студии, clang&gcc не принимают этот код.