Это один из тех часто задаваемых вопросов, которые имеют разные подходы, сходные, но не совсем одинаковые. Три подхода отличаются тем, кто вы объявляете себя другом вашей функции, а затем о том, как вы его реализуете.
экстраверт
Declare все конкретизации шаблона, как друзья. Это то, что вы приняли в качестве ответа, а также то, что предлагает большинство других ответов. В этом подходе вы безосновательно открываете свой конкретный экземпляр 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, который встраивание оператор --which также является хорошей идеей, и по другим причинам:
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...
}
}
Был недавний вопрос об этом, которое может оказаться полезным: http://stackoverflow.com/questions/4571611/virtual-operator/ –
@ Daniel - проблема не возникает при перегрузке для класса шаблона – starcorn
Я думаю, что лучше не изменять вопрос с заданным ответом. Это затрудняет определение исходной проблемы. Возможно, вам захочется добавить ** EDIT ** в конце с изменением, которое * решило * проблему, но я нахожу ее сбивающей с толку, когда вопросы меняются сверхурочно, и мне приходится подтягивать историю, чтобы увидеть, что на самом деле было задано в первое место. –