2011-01-11 2 views
36

Я уже пару раз задал вопрос о моей проблеме в stackoverflow, и ни одна из них, похоже, не решила мою проблему. Или я, возможно, сделал это неправильно ... Перегруженный <<, если я ввожу его в встроенную функцию. Но как мне заставить его работать в моем случае?оператор перегрузки друга << для шаблона класса

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; 
} 
+0

Был недавний вопрос об этом, которое может оказаться полезным: http://stackoverflow.com/questions/4571611/virtual-operator/ –

+0

@ Daniel - проблема не возникает при перегрузке для класса шаблона – starcorn

+5

Я думаю, что лучше не изменять вопрос с заданным ответом. Это затрудняет определение исходной проблемы. Возможно, вам захочется добавить ** EDIT ** в конце с изменением, которое * решило * проблему, но я нахожу ее сбивающей с толку, когда вопросы меняются сверхурочно, и мне приходится подтягивать историю, чтобы увидеть, что на самом деле было задано в первое место. –

ответ

100

Это один из тех часто задаваемых вопросов, которые имеют разные подходы, сходные, но не совсем одинаковые. Три подхода отличаются тем, кто вы объявляете себя другом вашей функции, а затем о том, как вы его реализуете.

экстраверт

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... 
    } 
} 
+5

так понятнее, чем выбранный ответ. – TheInvisibleFist

+0

Реализация функции вне класса дает неопределенную ссылку. Внутри класса я получаю проблему, как только я явно запускаю класс хоста более чем одним типом. – dgrat

+0

@dgrat: Я не могу отлаживать код, который я не вижу, но он работает. Является ли друг зависимым от любого из аргументов шаблона? Если это не так, вы получите проблемы с несколькими определениями, поскольку разные экземпляры будут генерировать точно такую ​​же * функцию (или нарушение ODR, если подпись функции такая же, но тело отличается). –

11

Вы не можете объявить друга, как это, вам нужно указать другой тип шаблона для него.

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.. 
} 
+0

спасибо, что эта работа отредактировала мой вопрос с помощью этого кода, я буду отмечать это как ответ, как только тикер спустится. – starcorn

+4

Обратите внимание, что это не объявление конкретного экземпляра 'operator <<' как друга, а всего ** всех ** экземпляров, включая любую специализацию шаблона. См. Ответ [здесь] (http://stackoverflow.com/questions/4660123/overloading-friend-operator-for-template-class/4661372#4661372) –

+0

@starcorn вы должны изменить свой выбранный ответ, чтобы обеспечить лучший ответ и это должен быть ответ Дэвида Родригеса. –

2

Это работало для меня без каких-либо предупреждений компилятора.

#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; 
} 
+0

Да, я уже это сделал, но что о том, если я не хочу 'operator <<' как встроенную функцию? – starcorn

+0

@starcorn: Упрощенный метод/функция встроена (неявно или явно) имеет мало что делать, когда функция действительно встроена в код. Поэтому это бессмысленное беспокойство. –

+0

+1. @starcorn: Это решение лучше, чем принятое. Разница тонкая, но в принятом ответе вы объявляете все экземпляры (и возможные специализации) «operator <<' в качестве друзей, в то время как в этом решении вы только предоставляете доступ к экземпляру 'operator <<', который имеет тот же тип. Кроме того, в качестве побочного эффекта определения 'operator <<' внутри класса вы ограничиваете видимость этого 'operator <<' только теми случаями, когда один из двух аргументов является 'D' - компилятор даже не будет рассмотрите перегрузку 'operator <<', если только один аргумент не является «D ». –

0

Здесь вы идете:

#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; 
} 
+0

Я не думаю, что это должно скомпилировать: 'template ostream & operator << (ostream & os, класс D const & rhs)'. Выбранный тип недопустим в объявлении параметра, а 'typename' требует квалифицированного id. –

+0

@Gene: хмм. он компилируется для меня на самом высоком уровне с отключенными расширениями MS. –

+0

Он не компилируется с g ++, и я доверяю компилятору в этом. Второй аргумент в 'operator <<' есть 'class D ', и я думаю, что это неверно. Вместо этого я использовал бы 'D ''. Ключевое слово 'class' является необязательным там (99,9% случаев), но использование' typename' не является одним из двух способов использования: оно было бы недопустимым в качестве замены 'class', и оно должно идентифицировать, что зависимое имя в шаблоне фактически является типом. –

0

Я думаю, что вы не должны делать друг в первую очередь.

Вы можете создать вызов печати общественный метод, что-то вроде этого (для не шаблонного класса):

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); 
} 

Я думаю, что он должен работать и для класса шаблонов, но я еще не тестировал.

Смежные вопросы