Это один из тех часто задаваемых вопросов, у которых разные подходы, которые похожи, но на самом деле не одинаковы.Три подхода различаются в том, кого вы объявляете другом вашей функции, а затем в том, как вы ее реализуете.
Экстраверт
Объявите все экземплярышаблон как друзья.Это то, что вы приняли в качестве ответа, а также то, что предлагает большинство других ответов.При таком подходе вы без необходимости открываете свою конкретную инстанцию D<T>
, объявляя друзьям все operator<<
инстанцирования.Таким образом, std::ostream& operator<<( std::ostream &, const D<int>& )
имеет доступ ко всем внутренним элементам D<double>
.
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
}
Интроверты
Объявляют только конкретную реализацию оператора вставки какдруг.D<int>
может понравиться оператор вставки, когда он применяется к самому себе, но он не хочет иметь ничего общего с std::ostream& operator<<( std::ostream&, const D<double>& )
.
Это может быть сделано двумя способами, простым способом, как предложил @Emery Berger,который является оператором, что также является хорошей идеей по другим причинам:
template <typename T>
class Test {
friend std::ostream& operator<<( std::ostream& o, const Test& t ) {
// can access the enclosing Test. If T is int, it cannot access Test<double>
}
};
В этой первой версии вы не создаете шаблон operator<<
, а скореене шаблонная функция для каждого экземпляра шаблона Test
.Опять же, разница невелика, но это в основном эквивалентно добавлению вручную: std::ostream& operator<<( std::ostream&, const Test<int>& )
при создании экземпляра Test<int>
и другой аналогичной перегрузке при создании экземпляра Test
с помощью double
или с любым другим типом.
Третий вариант более громоздкий.Не вставляя код и используя шаблон, вы можете объявить один экземпляр вашего шаблона другом вашего класса, не открывая себя для всех других экземпляров:
// Forward declare both templates:
template <typename T> class Test;
template <typename T> std::ostream& operator<<( std::ostream&, const Test<T>& );
// Declare the actual templates:
template <typename T>
class Test {
friend std::ostream& operator<< <T>( std::ostream&, const Test<T>& );
};
// Implement the operator
template <typename T>
std::ostream& operator<<( std::ostream& o, const Test<T>& t ) {
// Can only access Test<T> for the same T as is instantiating, that is:
// if T is int, this template cannot access Test<double>, Test<char> ...
}
Использование экстраверта
Тонкое различие между этим третьим вариантом и первым заключается в том, насколько вы открыты для других классов.Примером злоупотребления в версии extrovert может быть тот, кто хочет получить доступ к вашим внутренним ресурсам и делает это:
namespace hacker {
struct unique {}; // Create a new unique type to avoid breaking ODR
template <>
std::ostream& operator<< <unique>( std::ostream&, const Test<unique>& )
{
// if Test<T> is an extrovert, I can access and modify *any* Test<T>!!!
// if Test<T> is an introvert, then I can only mess up with Test<unique>
// which is just not so much fun...
}
}