Say you want to define a class template,
Foo<T>
, whose instantiations:
1. have a string representation
2. define operator<< so that it can be directed to cout (console).
How should this template class be defined?
The following definition works but has some drawbacks:
Q2. You have to define a new operator<<(ostream& os, const Foo<T>& _type) overload for additional type parameter.
Q3. The implementation of operator<<(ostream& os, const Foo<T>& _type) are very similar: T should have a string representation, and string representations of Foo<T>, for all T, are the same.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
#include <iostream>
#include <string>
#include <sstream>
using std::string;
using std::stringstream;
using std::cout;
using std::endl;
using std::ostream;
// Bar is a user-defined type of no significance; just a client class to be used to instantiate Foo.
struct Bar {
int barNum;
Bar(int b) : barNum(b) {}
friend std::ostream& operator<<(std::ostream& os, const Bar& obj)
{
return os << "barNum:" << obj.barNum;
}
};
// Class template of interest:
// Q1. How to define this template class so that Foo<int>, Foo<string>, Foo<Bar> instantiations have string representations?
template <typename T>
struct Foo
{
int fooNum = 0;
T val;
Foo(int someFooNum, T someVal) : fooNum(someFooNum), val(someVal) {}
template <typename U>
friend std::ostream& operator<<(std::ostream& os, const Foo<U>& t_Foo);
};
// Use: os << Foo<int>
ostream& operator<<(ostream& os, const Foo<int>& t_int)
{
std::stringstream ss;
ss << "Foo<int>(" << t_int.fooNum << "," << t_int.val << ")";
os << ss.str();
return os;
}
// Use: os << Foo<string>
ostream& operator<<(ostream& os, const Foo<string>& t_str)
{
std::stringstream ss;
ss << "Foo<string>(" << t_str.fooNum << "," << t_str.val << ")";
os << ss.str();
return os;
}
// Use: os << Foo<Bar>
ostream& operator<<(ostream& os, const Foo<Bar>& t_bar)
{
std::stringstream ss;
ss << "Foo<Bar>(" << t_bar.fooNum << "," << t_bar.val << ")";
os << ss.str();
return os;
}
void Example()
{
Foo<int> f_int(1, -10);
cout << f_int;
cout << endl;
Foo<string> f_str(2, "Alice");
cout << f_str;
cout << endl;
Foo<Bar> f_bar(3, Bar(30));
cout << f_bar;
cout << endl;
}
int main()
{
Example();
}
| |
Foo<int>(1,-10)
Foo<string>(2,Alice)
Foo<Bar>(3,barNum:30)
|
I tried to address Q2. and Q3. by using the Commonly-Recurring Template Pattern (CRTP) to address the duplication of the operator<< definitions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
struct Foo
{
int fooNum = 0;
Foo(int someFooNum) : fooNum(someFooNum) {}
};
template <typename T>
struct FooCRTP : public Foo
{
T val;
FooCRTP(int someFooNum, T someVal) : Foo(someFooNum), val(someVal) {}
template <typename T>
friend ostream& operator<<(ostream& os, const FooCRTP<T>& t_Foo)
{
stringstream ss;
ss << "Foo<T>(" << t_Foo.fooNum << "," << val << ")";
os << ss.str();
return os;
}
};
void Example()
{
FooCRTP<int> f_int(1, -10);
cout << f_int;
cout << endl;
FooCRTP<string> f_str(2, "Alice");
cout << f_str;
cout << endl;
FooCRTP<Bar> f_bar(3, Bar(30));
cout << f_bar;
cout << endl;
}
| |
But I get the following compiler error:
1>D:\...\Scrapcode\ex_Templates\ex_Templates.cpp(41,21): error C2995: 'std::ostream &operator <<(std::ostream &,const FooCRTP<T> &)': function template has already been defined
1>D:\...\ex_Templates\ex_Templates.cpp(41,21): message : see declaration of 'operator <<'
1>D:\...\ex_Templates\ex_Templates.cpp(57,27): message : see reference to class template instantiation 'FooCRTP<std::string>' being compiled
1>D:\...\ex_Templates\ex_Templates.cpp(62,5): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'FooCRTP<Bar>' (or there is no acceptable conversion)
|
I think the compiler error is saying that I'm breaking the One-Definition-Rule with the redefinition of operator<< for FooCRTP<std::string> but I don't see where that happens.