Reputation: 4862
I often want to write an stl container to an ostream. The following code works fine (at least for vector and list):
template< typename T ,template<typename ELEM, typename ALLOC=std::allocator<ELEM> > class Container >
std::ostream& operator<< (std::ostream& o, Container<T>const & container){
typename Container<T>::const_iterator beg = container.begin();
while(beg != container.end()){
o << *beg++;
if (beg!=container.end()) o << "\t";
}
return o;
}
Now I want to extend this code to support customizable separators. The following approach obviously does not work since the operator is supposed to take only two parameters.
template< typename T ,template<typename ELEM, typename ALLOC=std::allocator<ELEM> > class Container >
std::ostream& operator<< (std::ostream& o, Container<T>const & container,char* separator){
typename Container<T>::const_iterator beg = container.begin();
while(beg != container.end()){
o << *beg++;
if (beg!=container.end()) o << separator;
}
return o;
}
Can something like this be achieved without resorting to singletons or global variables ?
The ideal would be to introduce a custom flag or stream manipulator such as std::fixed
. One would then be able to write
std::cout << streamflags::tabbed << myContainer;
Upvotes: 4
Views: 1498
Reputation: 52471
You can write your own manipulator. basic_ostream
provides operator<<
overloads that take a function pointer (see (9) at the link), and call that function passing the stream itself as a parameter. So the manipulator is simply a function name.
Alternatively, a manipulator could be an object with a suitable operator<<
. This would allow you to write, say, cout << setSeparator(',') << myContainer;
. Here, setSeparator
is a class with setSeparator(char)
constructor.
Finally, ios_base
provides xalloc
, iword
and pword
members that essentially allow one to associate arbitrary information with a particular stream instance. That is how your manipulator can communicate with your operator<<(Container)
. You do need one global variable, to store an index allocated for you by xalloc
.
Upvotes: 5
Reputation: 10982
To complete @Igor-Tandetnik answer, a simple (not thread safe) more explicit example:
#include <iostream>
#include <vector>
namespace MyNamespace
{
namespace IO
{
enum class OrientationEnum
{
Row,
Column
};
struct State
{
OrientationEnum orientation = OrientationEnum::Row;
};
static State currentState;
template <typename CharT, typename Traits>
inline std::basic_ostream<CharT, Traits>& rowOriented(
std::basic_ostream<CharT, Traits>& os)
{
currentState.orientation = OrientationEnum::Row;
return os;
}
template <typename CharT, typename Traits>
inline std::basic_ostream<CharT, Traits>& columnOriented(
std::basic_ostream<CharT, Traits>& os)
{
currentState.orientation = OrientationEnum::Column;
return os;
}
}
template <typename T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& toPrint)
{
switch (IO::currentState.orientation)
{
case IO::OrientationEnum::Column:
for (const auto& e : toPrint)
{
out << e << "\n";
}
break;
case IO::OrientationEnum::Row:
for (const auto& e : toPrint)
{
out << e << " ";
}
break;
default:
break;
}
return out;
}
}
//////////////////////////////////////////////////////////////////
using namespace MyNamespace;
int main()
{
std::vector<int> v(5,0); // A 5-vector of 0
// If you want to save your state
// auto savedState = IO::currentState;
std::cout << "\nBy row\n"
<< IO::rowOriented << v
//
<< "\nBy column\n"
<< IO::columnOriented << v;
// IO::currentState = savedState;
}
You can compile and run it.
g++ streamExample.cpp -o streamExample
./streamExample
The output is:
By row
0 0 0 0 0
By column
0
0
0
0
0
Upvotes: 1