2011-02-07 5 views
2

давайте рассмотрим этот код, чтобы реализовать оператор < < для двух классов:оператора перегрузки << для шаблона класса

#include <iostream> 

using std::cout; 
using std::endl; 

class A 
{ 
    int a1_; 
public: 
    A(int a1) : a1_(a1){} 
    std::ostream& print(std::ostream& os) const 
    { 
     return os << "a1_ : " << a1_ << endl; 
    } 
}; 

class B 
{ 
    int b1_; 
    double b2_; 
public: 
    B(int b1,double b2) : b1_(b1),b2_(b2){} 
    std::ostream& print(std::ostream& os) const 
    { 
     os << "b1_ : " << b1_ << endl; 
     os << "b2_ : " << b2_ << endl; 
     return os; 
    } 
}; 

std::ostream& operator<<(std::ostream& os, const A& in) 
{ 
    return in.print(os); 
} 

std::ostream& operator<<(std::ostream& os, const B& in) 
{ 
    return in.print(os); 
} 

int main(int argc,char* argv[]) 
{ 
    A myA(10); 
    B myB(20,30.14); 

    cout << myA << myB << endl; 
    return 0; 
} 

Потому что я ленивый, я хотел бы представить версию шаблона оператора < < вместо две версии, как указано выше. Я могу легко его заменить:

template< class T> 
std::ostream& operator<<(std::ostream& os, const T& in) 
{ 
    return in.print(os); 
} 

Пока все хорошо. Если у меня есть несколько классов, я могу реализовать оператор < < за один раз. Проблема начинается, когда один из моих классов является шаблоном класса. Давайте рассмотрим предыдущий пример, но с шаблоном класса B:

#include <iostream> 

using std::cout; 
using std::endl; 

class A 
{ 
    int a1_; 
public: 
    A(int a1) : a1_(a1){} 
    std::ostream& print(std::ostream& os) const 
    { 
     return os << "a1_ : " << a1_ << endl; 
    } 
}; 

template <class T> 
class B 
{ 
    int b1_; 
    T b2_; 
public: 
    B(int b1,T b2) : b1_(b1),b2_(b2){} 
    std::ostream& print(std::ostream& os) const 
    { 
     os << "b1_ : " << b1_ << endl; 
     os << "b2_ : " << b2_ << endl; 
     return os; 
    } 
}; 


std::ostream& operator<<(std::ostream& os, const A& in) 
{ 
    return in.print(os); 
} 

template <class T> 
std::ostream& operator<<(std::ostream& os, const B<T>& in) 
{ 
    return in.print(os); 
} 

int main(int argc,char* argv[]) 
{ 
    A myA(10); 
    B<A> myB(20,myA); 

    cout << myA << myB << endl; 
    return 0; 
} 

Эта версия работает и у меня есть ожидаемый результат, однако я предоставил два оператора < < функции (по одному для каждого класса), давайте представим, что у меня есть 200 классы, которые уже реализуют публичную версию & print (ostream & os) const. Некоторые из них - это класс шаблонов (с несколькими параметрами).

Как я могу написать версию шаблона оператора < < в этом сценарии?

Спасибо вам за помощь.

+3

Ваш первый код также работает для шаблонов классов. Однако будьте осторожны, что он будет работать для * всех * классов, которые не определяют собственный 'operator <<', и это, вероятно, нежелательно. В частности, такая функция уже определена где-то в стандартной библиотеке, и вы обязаны нарушать одно правило определения с помощью вашего определения. –

+0

@ Konrad: Что ты имеешь в виду? Это никогда не должно быть причиной ошибки «no operator << для XXX»? – UncleBens

+0

@ Konrad: разве это не лучшее совпадение, если оно определено для определенного класса? –

ответ

7

То же, что и выше:

template< class T> 
std::ostream& operator<<(std::ostream& os, const T& in) 
{ 
    return in.print(os); 
} 

Однако «поймать все» перегрузки, как это немного, как динамит рыбалка. Вы можете ограничить диапазон оператора для всех T, которые определяют подходящий элемент «печать» с использованием SFINAE (http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error):

template<int X, typename T> 
struct enabler 
{ 
    typedef T type; 
}; 
template<class T> 
typename enabler< sizeof(&T::print), std::ostream&>::type 
operator << (std::ostream &o, const T &t) 
{ 
    t.print(o); 
    return o; 
} 

Это эффективно отключает оператор < < при поиске подходящей перегрузки, если T имеет не член print(std::ostream&)

+0

Я пробовал это, я использую visual studio 2010, и компилятор дает мне одну ошибку (ошибка C2593: «оператор <<» неоднозначна) внутри функции A :: print. –

+1

Вот почему я назвал этот динамит рыбалкой. Вы поймаете больше, чем вы когда-либо хотели ... «Энаблер» должен это исправить. –

+0

Это не сработает с ADL, хотя, конечно, каждый объект живет в глобальном пространстве имен. Хороший «enabler», кстати, не думал об использовании 'size_of'. –

0

Это, на самом деле, что Concepts предназначались. Вы можете имитировать их с помощью Boost.Concepts.

Однако есть одна проблема с вашим решением: поиск зависимых аргументов.

При использовании оператора, он должен быть:

  • либо в текущей области (поиск будет излучать наружу, чтобы рассмотреть все более и более глобальный охват)
  • или в пространстве имен одного из аргумент.

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

Предлагаю обман.

Если вы заключаете std::ostream& в классе своих собственном, вы можете в своем пространстве имен, обеспечиваете все операторские перегрузки, которые вы хотите для:

namespace X { 

struct MyStream 
{ 
    MyStream(std::ostream& o): _o(o) {} 
    std::ostream& _o; 
}; 

template <typename T> 
MyStream& operator<<(MyStream& s, T const& t) 
{ 
    t.print(s._o); 
    return s; 
} 

} // namespace X 

Вы можете добавить оппортунистические перегрузки для распространенных типов:

inline MyStream& operator<<(MyStream& s, bool b) 
{ 
    s._o << (b ? 'Y' : 'N'); 
    return s; 
} 

без риска столкновения с функциями, определенными в std.

Обратите внимание, что он торгует переработкой иерархии классов (с общим PrintableInterface тоже будет замечательно) и до обработки вызовов. Последнее может быть выполнено с помощью поиска и замены.

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