Martin
Martin

Reputation: 4862

How to write an ostream operator with custom flags

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

Answers (2)

Igor Tandetnik
Igor Tandetnik

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

Picaud Vincent
Picaud Vincent

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

Related Questions