Click here to Skip to main content
Click here to Skip to main content

iostream modifiers

By , 14 Jul 2002
 

Overview

Having previously written an article on iostream inserters and extractors, I decided to write an article on modifiers.  Most people use iostream modifiers without even realising.  std::endl is the most common modifier, it is simply an item that can be passed into a stream that affects the output.

std::endl

We're going to start by picking on std::endl.  How often do you see code like this ?

 for(int i = 0; i < 100; ++i) myStream << "This is function 
            number " << lNum << " " << m_szArray[i] << endl;

What is often not understood is that std::endl does not just pass in a line break.  It also sends a flush to the stream, which can be a real performance hit, if done as seen above.  Basically, a buffered stream will unload when flush is called, or the stream is destroyed.  The advantage of the buffer is almost entirely lost in the above case.  So, as a simply first example, I am going to show how to create a new modifier which does the line break without the flush.  It looks like this:

#include <iosfwd>

class Newline
{
public:
    friend std::ostream & operator <<(std::ostream &os, const Newline nl)
    {
        os << "\r\n";
        return os;
    }
};

This leaves us able to create a new line like this:

cout << "Here is my line " << Newline();

It works like this.  Our code creates a new, unnamed instance of our class.  During it's (brief) lifetime, the stream inserter is called, and the instance is passed in.  Our implementation of a stream operator is rudimentary, it simply passes in the new line and returns the stream object (as it should). 

Gettin' jiggy with it

The prior example was deliberately simple in order to illustrate the core functionality which any modifier will need to have.  Our next will be a little more complex: it will accept parameters.  In my work I do a lot of interacting with databases, and I quite often construct queries using an ostringstream.  Under those circumstances I am quite often querying a range based on date, and so I wrote a modifier to insert a datestamp.  We will build this modifier now, a step at a time.

Constants

The first thing we will need is values to pass into the constructor.  I have built only four, but it will be easy for you to add more.  Some obvious ones to add would be dd/mm/yyyy and mm/dd/yyyy formats.  For my purposes, we generally want ISO8601 format, with or without the time, and I've added one which includes the month in a text format, which is the other format that is universally readable ( i.e. it does not cause confusion as to which number is the month and which is the day ).  After much agonising, I elected to include this code in namespace std, because while overall I would not use std as a dumping ground for stuff I wanted to protect with a namespace, this code does interact directly with the std library and it's not illogical for it to live with it.

namespace std
{
    const int DT_ISO8601            = 1;
    const int DT_ISO8601DateOnly    = 2;
    const int DT_DD_MMM_YYYY        = 3;
    const int DT_HH_MM              = 4;
    
    const std::string DateStamp::Dates [12] = 
    {
        "Jan.", "Feb.", "March", "Apr.", "May", "June", 
        "July", "Aug", "Sept.", "Oct.", "Nov.", "Dec."
    };

The constructor

Our constructor will use the keyword explicit to ensure that no implicit conversions take place with values passed into it.  All parameters have default values, so it is easy to build our preferred datestamp format.  The parameters are the format constant, an offset ( so you can pass in -7 to get the date a week ago, or 1 to get tomorrow's date ), and the delimiters used between date elements and time elements.  Note I have used strings instead of chars so you can have multicharacter delimiters if you want to.

class DateStamp
    {
    public:
        explicit DateStamp(int nType = DT_ISO8601, int nOffset = 0, 
                           std::string sDateDelimiter = "-", 
                           std::string sTimeDelimiter = ":") 
            : datetype(nType), dateDelimiter(sDateDelimiter), 
              timeDelimiter(sTimeDelimiter), offset(nOffset) {}

The inserter

The guts of the inserter are reasonable obvious and uninteresting.  I use ::time and ::localtime to get to the point of having a tm struct with the timestamp in it, then do a switch on the format value in order to figure out what to stream into a stringstream, which we then dump into the target stream.  Probably the most important thing is to note that it is defined inside the class, and is defined as a friend of the class.  This means we can make our variables private, and still have access to them within the inserter.

 friend std::ostream & operator  <<(std::ostream &os, const DateStamp &mm) 
{
    time_t lTime; ::time(&lTime); 
    lTime += mm.offset * 86400; // 86400 seconds in a day 
    tm* ptmDate = ::localtime(&lTime); 
    
    std::ostringstream ss;
                        
    ss.fill('0'); 
    switch(mm.datetype) 
    { 
    case DT_ISO8601: 
    case DT_ISO8601DateOnly: 
        ss << ptmDate->tm_year + 1900 << mm.dateDelimiter;
        ss << std::setw(2) << ptmDate->tm_mon + 1 << mm.dateDelimiter;
        ss << std::setw(2) << ptmDate->tm_mday;
                
        if (mm.datetype == DT_ISO8601DateOnly) break; 
        
        ss << "T" << std::setw(2) << ptmDate->tm_hour << mm.timeDelimiter; 
        ss << std::setw(2) << ptmDate->tm_min << mm.timeDelimiter; 
        ss << std::setw(2) << ptmDate->tm_sec; 
        break; 
    case DT_DD_MMM_YYYY: 
        ss << std::setw(2) << ptmDate->tm_mday << mm.dateDelimiter; 
        ss << Dates[ptmDate->tm_mon] << mm.dateDelimiter 
           << ptmDate->tm_year + 1900; 
        break;
    case DT_HH_MM:
        ss << std::setw(2) << ptmDate->tm_hour << mm.timeDelimiter;
        ss <<    std::setw(2) << ptmDate->tm_min;
        break; 
    }
    
    os << ss.str();
    return os;
};

Modifiers with state

The date modifiers above all force a month or day less than 10 to have a preceding 0 by passing in first std::setw(2).  How does this work ? Obviously at the point that the modifier is passed in, iostreams does not yet know the value it needs to force to this width.  The answer is that modifiers can have state.  It is a beautiful thing, and it works like this.  When we write a modifier which will have state, we put a static int into the class declaration, and we initialise it with a call to std::ios_base::xalloc().  This returns an int, which could well be different between times that it is run.  I believe it is an index into a linked list ( only because that makes the most sense ), but whatever it is, it can then be used to set and get either a void *, or an integer, using either the iword or pword functions, which are present in your stream object passed in, but the values are shared through iostreams, so if you wanted to set or get a value and did not have a stream to use, you could just as well use cout, or any other standard stream, or even construct one ( although I don't know why you would ).  To illustrate, our final example is a simple class which is designed to hold someone's name details.  The constructor takes two strings, a first name and a second name.  We once again place these in std, although I usually would put them elsewhere, I don't expect anyone to use this example apart from for illustrative purposes.  We also again set values for the formats we will be able to use.

namespace std
{
    const int NAME_FIRST_LAST    = 1;
    const int NAME_LAST_FIRST    = 2;
    const int NAME_INITAL_LAST   = 3;
    const int NAME_INITIALS      = 4;

    class CName
    {
    friend class NameFormat;
    public:
        CName(std::string first, std::string last)
                            : m_sFirst(first), m_sLast(last)
        {};

Pretty straightforward stuff so far.  The next line is where things get interesting.  For the sake of clarity, I have shown the variable inside the class scope, and then shown how we declare it's value outside it.

class CName
{
...
    static int GetAlloc(){return m_nAlloc;}; 
private:
    std::string m_sFirst, m_sLast; 
    static const int m_nAlloc;
...
}

const int CName::m_nAlloc = std::ios_base::xalloc();     

As I've already mentioned, the call to xalloc returns an index which we will then use to pass values through iostreams.  I say 'through', because the fact is that our modifier will use this index for the purpose of storing a value, and the inserter for the class will pull it out and use it to decide how to format the output.  It is therefore a matter of personal taste if you feel the value belongs in the class or in the modifier.  I go for the class, because the modifier is defined inside the class also.  So inside the class definition, we define our inserter as follows:

friend std::ostream & operator <<(std::ostream &os, const CName &nm)
{
    if (!os.good())
        return os;

    std::ostream::sentry sentry(os);

    if(sentry)
    {
        std::ostringstream ss;

        switch(os.iword(nm.m_nAlloc))
        {
        default:
        case NAME_FIRST_LAST:
            ss << nm.m_sFirst << " " << nm.m_sLast;
            break;
        case NAME_LAST_FIRST:
            ss << nm.m_sLast << ", " << nm.m_sFirst;
            break;
        case NAME_INITAL_LAST:
            ss << nm.m_sFirst[0] << ". " << nm.m_sLast;
            break;
        case NAME_INITIALS:
            ss << nm.m_sFirst[0] << nm.m_sLast[0];
            break;
        }

        os << ss.str();
    }

    return os;
};

If you'd like to know more about writing iostream inserters and extractors, please refer to my article on the subject.

All that remains is to define the stream modifier, which will set the value stored in iostreams and retrieved by our inserter.

class NameFormat
{
public:
    explicit NameFormat(int nFormat) : m_nFormat(nFormat){}
    template<class charT class Traits class="",>
    friend std::basic_ostream<charT Traits,> & operator <<  
                                 (std::basic_ostream<charT Traits ,>& os, 
                                 const NameFormat & nf)
    {
        os.iword(CName::GetAlloc()) = nf.m_nFormat;
        return os;
    }
private:
    int m_nFormat;     
};

And there it is.  We can now print different formats from the same name, simply by passing them in through our modifier.  Here is the full listing of the example program:

#include "stdafx.h"
#include <iostream>

#include "Date Inserter.h"
#include "Newline.h"
#include "Name.h"

using std::cout;
using std::cin;
using std::endl;
using std::CName;
using std::NameFormat;
using std::DateStamp;

int _tmain(int argc, _TCHAR* argv[])
{
    cout << "My output " << endl;
    cout << "My other output " << Newline();
    cout << "And some more.... " << Newline();
    CName name("Christian", "Graus");
    cout << name << Newline();
    cout << NameFormat(std::NAME_LAST_FIRST) << name << Newline();
    cout << NameFormat(std::NAME_INITAL_LAST) << name << Newline();
    cout << NameFormat(std::NAME_INITIALS) << name << Newline();
    cout << DateStamp() << Newline();
    cout << DateStamp(std::DT_ISO8601DateOnly) << Newline();
    cout << DateStamp(std::DT_DD_MMM_YYYY) << Newline();
    cout << DateStamp(std::DT_HH_MM) << Newline();
    int i;
    cin >> i;
    return 0;
}

and the output look like this:

My output
My other output
And some more....
Christian Graus
Graus, Christian
C. Graus
CG
2002-07-15T20:19:59
2002-07-15
15-July-2002
20:19

I hope you agree with me that iostreams is a highly flexible and useful framework.  Add the ability to define your own stream types and you have a system by which you can easily pass any information you please, where-ever you choose.  My detractors often bring up the fact that iostreams seems to add 80 odd kilobytes to your executable.  This is probably true (that is to say, I have not checked, but I believe the people who have told me).  However, this is hardly a concern to me, because I use iostreams *constantly*, and so see a lot of benefit for my 80k.  In the interest of a balanced account, however, I mention this so that you are aware of it when deciding if you will use them on a particular project or not.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Christian Graus
Software Developer (Senior)
Australia Australia
Member
Programming computers ( self taught ) since about 1984 when I bought my first Apple ][. Was working on a GUI library to interface Win32 to Python, and writing graphics filters in my spare time, and then building n-tiered apps using asp, atl and asp.net in my job at Dytech. After 4 years there, I've started working from home, at first for Code Project and now for a vet telemedicine company. I owned part of a company that sells client education software in the vet market, but we sold that and now I work for the new owners.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralCompiler errors.memberWREY4 Sep '03 - 11:33 
Because I have VC++ 6.0, I created a project file and added all the 'C++' files I downloaded (from your sample), to it, but when I tried compiling it, three errors resulted in the "Iostream Inserters.cpp" file corresponding to:
 
cout << NameFormat(std::NAME_LAST_FIRST) << name << Newline();
cout << NameFormat(std::NAME_INITAL_LAST) << name << Newline();
cout << NameFormat(std::NAME_INITIALS) << name << Newline();
stating:
 
error C2678: binary '<<' : no operator defined which takes a left-hand operand of type 'void' (or there is no acceptable conversion)
 
Eek! | :eek:
 
William
 
Fortes in fide et opere!
GeneralHelp neededsussAnonymous10 Jul '03 - 5:53 
Hy Cristian,
 
Maybe you have some ideas on writing a custom modifier for compressed stream. If you could check the posts at
 
http://www.codeproject.net/vcpp/stl/zipstream.asp#xx542353xx
 
and give us your (wize) advise Smile | :)
QuestionQuestion?memberRama Krishna18 Jul '02 - 7:17 
Good article as usual.
 
Some time back I wanted to do something like this :-
 
std::ostream os;
 
os << encodehtml(true) << "<TestTag>" << encodehtml(false) << "<TestTag>";
 
I wish to see output like this
 
& lt;TestTag& gt; <TestTag>
 
I was thinking this can be done using manipulators. But looks like this is not a possibility. Any ideas?
 
<i>Step back, rub your eyes, take a deep breath, stretch a bit, and reflect on the relative importance of CP, CG, the age / travel time sustained by supposedly 'fresh' cheese curds, and Life in General.</i> - <B>Shog<font color=green><sup>9</sup></font></B>
AnswerRe: Question?memberChristian Graus18 Jul '02 - 13:28 
You would need to create a class to hold the HTML, and then write a manipulator for that class. OR you could write an inserter for _bstr_t and cast your literals to _bstr_t in order for your inserter to be used. The other option is to pass the string into the actual manipulator, which would mean you would not require the boolean or any state. I would probably go for the last option, pass a string into a function that encodes the HTML and passes it into the stream.

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralRe: Question?memberRama Krishna18 Jul '02 - 14:50 
There should be some other way and that is what I am looking for. If you see the chapter on isotreams in Strourstrup book and look at one of the excercises for the chapter, you will see an excercise for writing an encrypt(k) modifier. How does he expect that to be solved?
 
Step back, rub your eyes, take a deep breath, stretch a bit, and reflect on the relative importance of CP, CG, the age / travel time sustained by supposedly 'fresh' cheese curds, and Life in General. - Shog9
GeneralRe: Question?memberChristian Graus18 Jul '02 - 14:57 
Maybe writing a local inserter for char* will hide the one in std ?

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralRe: Question?memberRama Krishna18 Jul '02 - 15:09 
Christian Graus wrote:
Maybe writing a local inserter for char* will hide the one in std ?
 
I don't think Stourstrup intends it to be that way. I was puzzled by this problems around 3 yrs back when I read that chapter. I thought for a while and gave it up that time. I was working on HTML reporting some time back and then I thought about this problem again. Now this article of yours have ignited the interest in it again.
 
I was thinking more in terms of direct access to the stream buffer and modifying the buffer directly in the manipulator. The solution is workable as long as the buffer doesnot get flushed. I don't know whether I am missing somethong or just dreaming of something impossible.
 
Step back, rub your eyes, take a deep breath, stretch a bit, and reflect on the relative importance of CP, CG, the age / travel time sustained by supposedly 'fresh' cheese curds, and Life in General. - Shog9
GeneralRe: Question?memberChristian Graus18 Jul '02 - 15:14 
Rama Krishna wrote:
I don't know whether I am missing somethong
 
I think so.
 
1. the modifier is called when the stream has no idea about the text that needs to be htmlised
 
2. how do you know the stream is buffered
 
3. how do you know it has not been flushed between
 
Are you looking at the C++ programming language 3rd ed ? If so, what page no., I will look at it when I get home.
 

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralRe: Question?memberRama Krishna18 Jul '02 - 18:16 
Unfortunately, I don't have the book with me. It is in the excecise of chapter on "Streams". The excercise asks to build a modifier encrypt(k) and is fairly easy to locate.
 
Step back, rub your eyes, take a deep breath, stretch a bit, and reflect on the relative importance of CP, CG, the age / travel time sustained by supposedly 'fresh' cheese curds, and Life in General. - Shog9
GeneralRe: Question?memberChristian Graus19 Jul '02 - 13:38 
I can't see the exercise, but I think I know the answer. An operation such as encryption should take place on ALL insertions, not just of a specified type. Therefore the answer is a stream object that can be tied to existing streams, and which checks the modifier to see if it should encrypt, decrypt, or pass unencumbered. This would then work with all << operators.

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralRe: Question?memberRama Krishna19 Jul '02 - 15:46 
Christian Graus wrote:
answer. An operation such as encryption should take place on ALL insertions, not just of a specified type. Therefore the answer is a stream object that can be tied to existing streams,
 
Makes sense. I am surprised you can't find the excercise. It's rated 3*. See again.
 
Step back, rub your eyes, take a deep breath, stretch a bit, and reflect on the relative importance of CP, CG, the age / travel time sustained by supposedly 'fresh' cheese curds, and Life in General. - Shog9
GeneralRe: Question?memberChristian Graus19 Jul '02 - 15:48 
Rama Krishna wrote:
I am surprised you can't find the excercise. It's rated 3*. See again.
 
I see it now. It's Q22. I think I'm right - you need to impliment your own stream that handles the modifier.

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralMy ComplimentsmemberRamon Casellas17 Jul '02 - 21:00 
Hi Christian,
 
Nice work. The article is clear, structured, progressive and comprehensive. What else?
 
Regards,
R.

GeneralRe: My ComplimentsmemberChristian Graus17 Jul '02 - 21:07 
Ramon Casellas wrote:
Nice work.
 
Thanks.
 
Ramon Casellas wrote:
What else?
 
What else would you like ? I'm thinking of doing some on stl algorithms next, but I'm open to suggestions.

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralGREAT ARTICLE..memberBernhard16 Jul '02 - 0:12 
thank you christian for your great article series.. every article shows me another part of the stl..
i think the biggest disadvantage for beginners in using the stl is that there are too less articles like yours around.. (cause other articles are often hard to grasp)..
i've started using vectors .. i like em
i've started using strings .. i like em
i've started using ostringstream .. i like em..
and now i think i start using modifiers (the date example is totally appealing.. cause i need stuff like this all the time.. but never thought about using modifiers)
 
the only problem.. the link for the source - files doesn't work for me..
 


"I'm from the South Bronx, and I don't care what you say: those cows look dangerous."
U.S. Secretary of State Colin Powell at George Bush's ranch in Texas
GeneralRe: GREAT ARTICLE..memberChristian Graus16 Jul '02 - 0:18 
Bernhard wrote:
and now i think i start using modifiers (the date example is totally appealing.. cause i need stuff like this all the time.. but never thought about using modifiers)
 
I'm glad - I use iostreams *constantly*, but I'll admit I write about a lot of this stuff as I learn in, usually because writing it crystalises my understanding, and then everyone corrects me where I am wrong Smile | :)
 
Bernhard wrote:
the only problem.. the link for the source - files doesn't work for me..
 
The link is wrong, remove the last letter prior to the word 'zip' and you'll be fine ( i.e. it's something like 'iostreammodifierscodex.zip', you need to remove the 'x', or whatever letter is there ). I'm going to write to Chris about it now.

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralRe: GREAT ARTICLE..memberChristian Graus16 Jul '02 - 1:34 
Just got the email, Chris has fixed the link. Thanks Chris !!!!

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralnewlinememberThomas Freudenberg15 Jul '02 - 21:33 
Hi Christian.
 
I suggest a more generic approach for the Newline
template<class charT, class traits>
std::basic_ostream<charT,traits>&
Newline (std::basic_ostream<charT,traits>& strm)
{
    strm.put(strm.widen('\n'));
    return strm;
}
, which is just a modified version of std::endl. BTW, the str.widen code respects the actual character set of the stream.
Another advantage of this version is that you have not to define a new class, but just a function.
 
Nevertheless an informativ article, Christian -> 5 points
 
Regards
Thomas
 
Sonork id: 100.10453 Thömmi

Disclaimer:
Because of heavy processing requirements, we are currently using some of your unused brain capacity for backup processing. Please ignore any hallucinations, voices or unusual dreams you may experience. Please avoid concentration-intensive tasks until further notice. Thank you.

GeneralRe: newlinememberDaniel Turini15 Jul '02 - 22:40 
That's why I love STL: no matter how tiny, stupid and easy your task is, like outputting a single char; you end writing and entire class and, wait, someone still finds a better way!
 

Concussus surgo.
When struck I rise.

GeneralRe: newlinememberChristian Graus15 Jul '02 - 22:44 
Yes, and one reason I love to write these articles is that I always learn something from the comments I get !!!

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralRe: newlinememberChristian Graus15 Jul '02 - 22:42 
Thomas Freudenberg wrote:
Another advantage of this version is that you have not to define a new class, but just a function.
 
You are very much correct, but the point of Newline is not to provide anything that would be used by anyone especially, rather it is meant to be the starting point of incrementally learning to build the most complex example provided. Nevertheless, as an example, yours is certainly superior.
 

 

 
Christian
 
come on all you MS suckups, defend your sugar-daddy now. - Chris Losinger - 11/07/2002

GeneralRe: newlinememberThomas Freudenberg15 Jul '02 - 22:49 
Christian Graus wrote:
rather it is meant to be the starting point of incrementally learning to build the most complex example provided.
 
...and it's a good starting point. I like to see more STL artciles by you, Christian. Your style is very clear and easy to follow.
 
Regards
Thomas
 
Sonork id: 100.10453 Thömmi
 

Disclaimer:
Because of heavy processing requirements, we are currently using some of your unused brain capacity for backup processing. Please ignore any hallucinations, voices or unusual dreams you may experience. Please avoid concentration-intensive tasks until further notice. Thank you.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 15 Jul 2002
Article Copyright 2002 by Christian Graus
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid