Printing containers in C++
Some useful facts:
- There is a
std::basic_ostreamclass template in c++ standard library. It allows you to output some things like strings and numbers. - Type of object
std::coutisstd::ostreamwich is a typedef ofstd::basic_ostream <char>(also known asstd::basic_ostream <char, std::char_traits <char>>). std::stringis a typedef forstd::basic_string <char>(also known asstd::basic_string <char, std::char_traits <char>, std::allocator <char>>)
How to output with ostream
To output with ostream, operator << should be defined. Lets implement one for outputting pair <int, int> and test it.
ostream & operator << (ostream & os, pair <int, int> const& x) {
os << x.first << ", " << x.second;
return os;
}
How to output container object ? Let's implement outputting for vector:
template <class T>
ostream & operator << (ostream & os, vector <T> const& x) {
os << "{ ";
for(auto& y : x) os << y << " ";
return os << "}";
}
And now for set:
template <class T>
ostream & operator << (ostream & os, set <T> const& x) {
os << "{ ";
for(auto& y : x) os << y << " ";
return os << "}";
}
And now for both:
template <class Container>
ostream & operator << (ostream & os, Container const& x) {
os << "{ ";
for(auto& y : x) os << y << " ";
return os << "}";
}
Oops
error: ambiguous overload for 'operator<<' (operand types are 'std::ostream {aka std::basic_ostream}' and 'const char [3]')
The error happens here os << "{ ";, here os << y << " "; and here os << "}";.
Somewhere deep in standard library header files many operators << for outputting basic_string and const char* are defined like this:
...
template <class CharT, class Traits, class Allocator>
basic_ostream <CharT, Traits> &
operator << (basic_ostream <CharT, Traits> & os, const basic_string <CharT, Traits, Allocator> & str);
...
template< class CharT, class Traits >
basic_ostream <CharT, Traits> & operator << (basic_ostream <CharT, Traits> & os, const char* s );
...
And they are conflicting with our container outputting operator.
The main part of this post
Look at this code.
template <class T>
void f (T const& v) {
cout << "T \n";
}
template <class T>
void f (vector <T> const& v) {
cout << "vector <T> \n";
}
void f (vector <int> const& v) {
cout << "vector <int> \n";
}
There are 3 overloads of one function f and they are not conflicting. The first one can take any possible argument. The second one is only for vectors. The third overload is for vector <int> objects only. For any argument compiler can choose which overload is more suitable. Everything looks pretty clear.
But what if we have 2 arguments ?
template <class T>
void f (vector <T> const& v, set <int> const& s) {
cout << "vector <T> and set <int> \n";
}
template <class T>
void f (vector <int> const& v, set <T> const& s) {
cout << "vector <int> and set <T> \n";
}
void test() {
auto set_str = set <string> {"1", "2", "3"};
auto set_int = set <int> {1, 2, 3};
auto vec_str = vector <string> {"1", "2", "3"};
auto vec_int = vector <int> {1, 2, 3};
f( vec_str, set_int );
f( vec_int, set_str );
}
/* output:
vector <T> and set <int>
vector <int> and set <T> */
There is a case when both overloads are equally suitable. If we try to call f( vec_int, set_int ) we'll get this: error: call of overloaded 'f(std::vector<int>&, std::set<int>&)' is ambiguous.
Back to the <<
So there are some << operators:
/* standart operator */
template <class CharT, class Traits, class Allocator>
basic_ostream <CharT, Traits> &
operator << (basic_ostream <CharT, Traits> & os, const basic_string <CharT, Traits, Allocator> & str);
/* standart operator */
template <class CharT, class Traits>
basic_ostream <CharT, Traits> & operator << (basic_ostream <CharT, Traits> & os, const char* s);
/* our operator */
template <class Container>
ostream & operator << (ostream & os, Container const& x);
If we try to output something which is not string (basic_string) or const char* everything is ok.
But if we try to cout << "heh" we get the situation as described above. Since cout hase type ostream(basic_ostream <char>) our operator is more sutable than standart operators with basic_ostream <CharT, Traits>. At the same time const char* is more sutable for "heh" than Container const&.
To fix our problem all we need to do is to replace ostream by basic_ostream <Ch, Tr>.
template <class Ch, class Tr, class Container>
basic_ostream <Ch, Tr> & operator << (basic_ostream <Ch, Tr> & os, Container const& x) {
os << "{ ";
for(auto& y : x) os << y << " ";
return os << "}";
}
What about associative containers ? We can't output them yet. The main feature of associative containers is to store pairs of key and value, mapped to it. For example we have some map object map <int, int> m. What we get by dereferensing iterator to first element of our map (in code *m.begin()) is literally pair of key and value pair <const int, int> &. Let's implement outputting for pairs.
template <class X, class Y>
ostream & operator << (ostream & os, pair <X, Y> const& p) {
return os << "[ " << p.first << ", " << p.second << "]" ;
}
Now we can output such things as map, unordered_map and even __gnu_pbds::tree and __gnu_pbds::cc_hash_table from ext/pb_ds/assoc_container.hpp.







