Недавно столкнулся с такой проблемой как осложнение operator<< для template class, и вот ищя ответ на этот вопрос, перерыл много вопросов на английском stack'e. Нашёл решение вот тут. Меня смутило такое решение проблемы -
template <typename T>
class Test {
template <typename U> // all instantiations of this template are my friends
friend std::ostream& operator<<( std::ostream&, const Test<U>& );
};
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& ) {
// Can access all Test<int>, Test<double>... regardless of what T is
}
Я не понимаю почему тут используется совсем другой template - template <typename U>, а тут такой template <typename T>.
Вроде как пытался перевести, но так и не понял. А может я не совсем понимаю работы friend?
На самом деле нет никакой разницы между template <typename T>, template <typename U>, template <class A> и template <class BestTypeEver>. Это просто псевдоним для типа, который будет выведен компилятором во время компиляции. Так что можете заменить эти имена так чтобы они вас не смущали:
template <typename T>
class Test {
template <typename U> // all instantiations of this template are my friends
friend std::ostream& operator<<( std::ostream&, const Test<U>& );
};
template <typename U>
std::ostream& operator<<( std::ostream& o, const Test<U>& ) {
// Can access all Test<int>, Test<double>... regardless of what U is
}
Что касается friend, это ключевое слово говорит то том, что ваш оператор << может обращаться к приватным и защищенным членам класса.
class A{
int _privateMember;
friend std::ostream& operator<<( std::ostream& o, const A& a);
}
std::ostream& operator<<( std::ostream& o, const A& a){
o << a._privateMember; //так можно только благодаря friend
return o;
}
Строго говоря, если вы можете добраться до всего что вам нужно через открытый интерфейс, то можно обойтись и без friend:
class A{
int _privateMember;
public:
int getPrivateMember() const{
return _privateMember;
}
}
std::ostream& operator<<( std::ostream& o, const A& a){
o << a.getPrivateMember();
return o;
}
Во-первых, как вам же ответили, шаблонный параметр - это лишь локальный формальный параметр, имя которого не имеет никакого значения. В точке friend-объявления оператора вы не можете использовать имя T, так как оно уже "занято" охватывающим шаблоном. А в точке определения оператора имя T свободно и его можно использовать.
Однако, во-вторых, стоит заметить, что в данном случае вы наблюдаете то, что можно назвать "ленивым" вариантом friend-объявления. Этот вариант более компактен, но в то же время формально избыточен, ибо он объявляет все специализации оператора << друзьями всех специализаций класса Test. В частности operator << <int> будет являться другом класса Test<double>. (Именно об этом написано в комментарии внутри определения оператора.)
В такой избыточной "перекрестной дружбе" нет ничего плохого или опасного, однако если она вам не нужна и вы хотите поступить более педантично, т.е сделать так, чтобы каждая специализация оператора дружила именно и только со "своей" специализацией шаблона класса Test, то писанины будет чуть побольше
template <typename>
class Test;
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& );
template <typename T>
class Test {
friend std::ostream& operator<< <>( std::ostream&, const Test& );
};
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& ) {
// Can access only Test<T>
}
Именно из-за несколько большей громоздкости такого педантичного варианта зачастую некоторые авторы предпочитают прибегать к "ленивому" варианту.
Сборка персонального компьютера от Artline: умный выбор для современных пользователей