Перегрузка оператора друга << для шаблона класса
Я прочитал пару вопросов относительно моей проблемы на StackOverflow.com сейчас, и ни один из них, похоже, не решил мою проблему. Или я, возможно, сделал это неправильно... перегруженный <<
работает, если я превращаю это в встроенную функцию. Но как мне заставить это работать в моем случае?
warning: friend declaration std::ostream& operator<<(std::ostream&, const D<classT>&)' declares a non-template function
warning: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here) -Wno-non-template-friend disables this warning
/tmp/cc6VTWdv.o:uppgift4.cc:(.text+0x180): undefined reference to operator<<(std::basic_ostream<char, std::char_traits<char> >&, D<int> const&)' collect2: ld returned 1 exit status
Код:
template <class T>
T my_max(T a, T b)
{
if(a > b)
return a;
else
return b;
}
template <class classT>
class D
{
public:
D(classT in)
: d(in) {};
bool operator>(const D& rhs) const;
classT operator=(const D<classT>& rhs);
friend ostream& operator<< (ostream & os, const D<classT>& rhs);
private:
classT d;
};
int main()
{
int i1 = 1;
int i2 = 2;
D<int> d1(i1);
D<int> d2(i2);
cout << my_max(d1,d2) << endl;
return 0;
}
template <class classT>
ostream& operator<<(ostream &os, const D<classT>& rhs)
{
os << rhs.d;
return os;
}
Ответы
Ответ 1
Это один из тех часто задаваемых вопросов, которые имеют разные подходы, которые похожи, но не совсем одинаковы. Три подхода отличаются тем, кто вы заявляете, что являетесь другом вашей функции, а затем о том, как вы его реализуете.
Экстраверт
Объявить все экземпляры шаблона в качестве друзей. Это то, что вы приняли в качестве ответа, а также то, что предлагает большинство других ответов. В этом подходе вы без необходимости открываете свой экземпляр 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> ...
}
Воспользовавшись экстравертом
Тонкая разница между этим третьим вариантом и первым заключается в том, насколько вы открываете другие классы. Примером злоупотребления в версии для экстравертов будет тот, кто хочет получить доступ к вашим внутренним частям и делает следующее:
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...
}
}
Ответ 2
Вы не можете объявить такого друга, вам нужно указать для него другой тип шаблона.
template <typename SclassT>
friend ostream& operator<< (ostream & os, const D<SclassT>& rhs);
отметить SclassT
, чтобы он не теневой classT
. При определении
template <typename SclassT>
ostream& operator<< (ostream & os, const D<SclassT>& rhs)
{
// body..
}
Ответ 3
Это работало для меня без каких-либо предупреждений компилятора.
#include <iostream>
using namespace std;
template <class T>
T my_max(T a, T b)
{
if(a > b)
return a;
else
return b;
}
template <class classT>
class D
{
public:
D(classT in)
: d(in) {};
bool operator>(const D& rhs) const {
return (d > rhs.d);
}
classT operator=(const D<classT>& rhs);
friend ostream& operator<< (ostream & os, const D& rhs) {
os << rhs.d;
return os;
}
private:
classT d;
};
int main()
{
int i1 = 1;
int i2 = 2;
D<int> d1(i1);
D<int> d2(i2);
cout << my_max(d1,d2) << endl;
return 0;
}
Ответ 4
Здесь вы идете:
#include <cstdlib>
#include <iostream>
using namespace std;
template <class T>
T my_max(T a, T b)
{
if(a > b)
return a;
else
return b;
}
template <class classT>
class D
{
public:
D(classT in)
: d(in) {};
bool operator>(const D& rhs) const { return d > rhs.d;};
classT operator=(const D<classT>& rhs);
template<class classT> friend ostream& operator<< (ostream & os, const D<classT>& rhs);
private:
classT d;
};
template<class classT> ostream& operator<<(ostream& os, class D<typename classT> const& rhs)
{
os << rhs.d;
return os;
}
int main()
{
int i1 = 1;
int i2 = 2;
D<int> d1(i1);
D<int> d2(i2);
cout << my_max(d1,d2) << endl;
return 0;
}
Ответ 5
Я думаю, вы не должны делать друга в первую очередь.
Вы можете создать публичный вызов вызова метода, что-то вроде этого (для класса без шаблона):
std::ostream& MyClass::print(std::ostream& os) const
{
os << "Private One" << privateOne_ << endl;
os << "Private Two" << privateTwo_ << endl;
os.flush();
return os;
}
а затем вне класса (но в том же пространстве имен)
std::ostream& operator<<(std::ostream& os, const MyClass& myClass)
{
return myClass.print(os);
}
Я думаю, что он должен работать и для класса шаблонов, но я еще не тестировал его.
Ответ 6
Это хорошая статья: C++ Классы шаблонов и подробности о функциях друзей https://web.mst.edu/~nmjxv3/articles/templates.html В нем также упоминается ответ Дэвида Родригеса-Дрибеаса.