I'm trying to overload my 'Log' class so that I can pass 'endl' to it. Given the obscure nature of the compiler errors for template classes, I'm not sure what to put on the 'right hand side'.

For example, these work fine. ..

Log & operator << (ostringstream & a)
{
		writeLog(a.str());
		return *this;
}
		
		
Log & operator << (const string & a)
{
		writeLog(a);
		return *this;
}

How do I do one that accepts endl?

This is the compiler's complaint:

line 62: Error: The operation "Log << std::basic_ostream<std::charT, std::traits>&(*)(std::basic_ostream<std::charT, std::traits>&)" is illegal.

<< moderator edit: added [code][/code] tags >>

(Sorry, posted something I thought was working but was not...question still stands!)

For somewhat obscure reasons, operator<< should be a non-member function:

ostream& operator<< ( ostream& out, const Log& log )
{
  log.writeLog ( out );
  return out;
}

If Log doesn't have a public interface that allows everyone to write the log somewhere, you'll need to make the function a friend of Log as well. Without seeing your Log class declaration, it's hard to tell exactly what you need, but if you define operator<< in the conventional way (as above), your endl problem just goes away.

How do I get from an ostream to a string or an ostringstream? (That's what my writeLog function expects..)

Here's Log.h:
----------------------------

#include "MainInclude.h"

#ifndef __LOG_CLASS__
#define __LOG_CLASS__


#define MAX_LOG_SIZE 5000

using namespace std;

class Log 
{
	private:	
		Date current;
		string fileName;
		ofstream *out;
		bool screenOutput;

		bool includeDate;
		string contentSeparator;
		
		bool isOpenFlag;
		

		
		

	public:	
	
	Log();
	Log( const string & file, bool withDate=false );
	~Log();
	
		
	bool isOpen () { return isOpenFlag; } 
	
	
	string getFileName() { return fileName; }
	void setContentSeparator( const string & sep );
	
	
	void open();
	void close();
	

	
	void writeLog ( const double & logLine );
	void writeLog ( const string & logLine );
	void writeLog ( const ostringstream & inputStream);
	void writeLog( const char * logLine, ... );
	void writeLog ( Printable &a );
	
	
	
	

	void setIncludeDate( bool doDate ) { includeDate = doDate; }
	void setFileName( string theFile) 
	{ 
		fileName = theFile; 
		screenOutput=false;
	}
	
/* THE NEW FUNCTION FOR ENDL */
	Log & operator << (ostream & a)
	{
		ostringstream o;
		o<<a;
		writeLog(o);
		return *this;
	}
	
	Log & operator << (ostringstream & a)
	{
		writeLog(a.str());
		return *this;
	}
		
		
	Log & operator << (const string & a)
	{
		writeLog(a);
		return *this;
	}
		
	Log & operator << ( const char * a)
	{
		writeLog(a);
		return *this;
	
	}
		
		
	Log & operator << ( const double & a)
	{
		writeLog(a);
		return *this;
	}
	
	/* To handle any class that extends Printable... function must support toString() */
	Log & operator << ( Printable &a )
	{
		
		writeLog( a.toString() );
		return *this;
	}
};
#endif

Here's attempted uisage:

Log * screenLog;
screenLog = new Log();
*screenLog<<"Done loading session, successful..."<<endl;

----------------

Error: The operation "Log << std::basic_ostream<std::charT, std::traits>&(*)(std::basic_ostream<std::charT, std::traits>&)" is illegal.

<< moderator edit: added [code][/code] tags >>

Can you show me the definitions of writeLog? Because it seems to me you're going about this in a funky way.

Edit: Unless, of course, you're trying to wrap and simulate a stream, where something like this is appropriate:

#include <iostream>
#include <string>

using namespace std;

class Header {
  string _date;
  string _name;
public:
  Header(const string& date, const string& name)
    : _date(date), _name(name) {}
  friend ostream& operator<<(ostream& out, const Header& h )
  {
    return out<< h._date <<" -- "<< h._name <<'\n';
  }
};

class Log {
  ostream& _out;
  Header _header;
  bool _first;
public:
  Log(const Header& header, ostream& out = cout)
    : _header(header), _out(out), _first(true) {};
  ostream& operator<<(const string& s)
  {
    if (_first) {
      _out<< _header <<'\n';
      _first = false;
    }
    return _out<< s;
  }
};

int main()
{
  Log log(Header("05-05-2005", "Narue's Log"));

  log<<"This is a test"<<endl;
  log<<"Another test"<<endl;
  log<<"Last test"<<endl;
}

This is Log.cpp.
-----------------

#include "Log.h"
 
 
 
void Log::setContentSeparator( const string & sep )
{
    contentSeparator = sep;    
}
 
Log::Log()
{
    screenOutput=true;
    isOpenFlag = false;
    out=NULL;
    fileName="CONSOLE";
    setContentSeparator ( ">");
    includeDate=false;
}
 
Log::Log( const string & file, bool withDate) 
{ 
    screenOutput=false;
    isOpenFlag = false;
    out=NULL;
    fileName = file;
    includeDate = withDate;
    setContentSeparator ( ">");
 
}
void Log::open()
{
 
    if ( screenOutput )
    {
        isOpenFlag = true;
        return;
    }
    if ( out != NULL )
        throw DHException( "File [" + fileName + "] is already open!");
 
    out = new ofstream(fileName.c_str(), ofstream::out);
 
 
    if ( !out->is_open() )
    {
 
        throw DHException( "Could not open file [" + fileName + "]!");
    }
 
    isOpenFlag = true;
 
}
 
void Log::writeLog( Printable &a )
{
    writeLog( a.toString() );
}
void Log::writeLog ( const ostringstream & inputStream)
{
        writeLog( inputStream.str());
}
 
void Log::writeLog( const double & logLine )
{
    ostringstream o;
    o<<logLine;
    writeLog( o.str() );
}
 
void Log::writeLog( const string & logLine )
{
 
    if ( !screenOutput )
    {
        if ( fileName=="" )
            throw DHException( "Log filename has not been set!");
 
        if ( out == NULL || !out->is_open() )
            throw DHException( "Could not write to log file [" + fileName + "]. File was never opened or was previously closed!");
 
    }
    if ( includeDate )
    {
        current.now();
        if ( !screenOutput )
            *out<<Date::getFormattedDate( current )<<contentSeparator<<logLine;
        else
        {
            cout<<Date::getFormattedDate( current )<<contentSeparator<<logLine;
            cout<<flush;
        }
 
    }
    else
    {
        if ( !screenOutput )
            *out<<logLine;
        else
        {
            cout<<logLine;
            cout<<flush;
        }
    }
 
    if ( !screenOutput )
    {
        out->flush();
 
        if ( !out->is_open() )    
            throw DHException ( "Could not write to log file [" + fileName + "] !");
 
    }
}
 
 
void Log::writeLog(const char * logLine, ... )
{
 
         va_list args;
     va_start (args, logLine);
char *dan = new char[MAX_LOG_SIZE];
long count = vsnprintf (dan, MAX_LOG_SIZE, logLine, args);
va_end (args);
 
            string temp;
 
 
            if ( count == -1 || count > MAX_LOG_SIZE )
            {
                temp=dan;
                temp+="...<LOG TRUNCATED>...";
            }
            else
                temp=dan;
 
            if ( dan !=NULL )
            {
                    delete dan;
            }
            writeLog( temp );
 
}
 
void Log::close()
{
    if ( !screenOutput )
    {
        if ( out !=NULL )
        {
            if ( out->is_open() )
                 out->close();    
             out=NULL;
        }
    }
}
Log::~Log()
{
 
    if ( out != NULL )
    {
        if ( out->is_open() )
            out->close();
 
        delete out;
    }
}

<< moderator edit: added [code][/code] tags >>

Okay, so what you want really is like my example. In that case, notice how every time I define operator<<, it returns an ostream reference. I do this so that chaining is possible:

log<< something << somethingelse <<endl;

When you return an ostream reference, endl should work fine.

Narue,

I'm having trouble understanding how this works. I took your sample, made some changes to remove some stuff (like the date, etc.) and it works nicely. My question is this. Is there a way to populate the _out not as part of the constructor? Also, is there an equivalent way of doing this to an ofstream? The reason I ask is that my Log class either writes to a file or to the console, and I would like to choose where it goes inside the body of the constructor... Also, is the only way to do this to create that Header class? There's no way to do it simply inside the Log class?

>and I would like to choose where it goes inside the body of the constructor...
You can. As defined, Log defaults to cout, and you can override that by passing a second argument. Say, a file stream:

ofstream out("somefile");
Log log(header, out); // Assuming a suitably defined header

>Also, is the only way to do this to create that Header class?
The header class is completely different. It's there merely as a convenience so that when you open a log, you know when it was created and it has a descriptive name. You can easily do without it:

#include <iostream>
#include <string>

using namespace std;

class Log {
  ostream& _out;
  bool _first;
public:
  Log(ostream& out = cout)
    : _out(out), _first(true) {};
  ostream& operator<<(const string& s)
  {
    return _out<< s;
  }
};

int main()
{
  Log log;

  log<<"This is a test"<<endl;
  log<<"Another test"<<endl;
  log<<"Last test"<<endl;
}

Thanks for all your help - I'll try it out and let you know.

By the way, can you explain this syntax? I've only seen it used for when you are calling the constructor of a parent class from child class... I know that you are setting the class variable _out with the out that was passed in, and setting the _first variable = true, but not sure why it needs to be done with this way rather than the doing it inside the {} . (I did notice that it complains about _out not being initialized if you try it any other way...)

Log(ostream& out = cout) : _out(out), _first(true) {};

Narue,

I rewrote the class as follows:

#include "MainInclude.h"
#ifndef __NEW_LOG__
#define __NEW_LOG__
class NewLog
{
 private:
 ostream& out;
   
   bool is_file;
   bool includeDate;
 public:
 NewLog(ostream& myout = cout) : out(myout)
 {
  includeDate = false;
 
 }
 
 NewLog( ofstream &o) : out(o)
 {
  includeDate = false;
  
  if ( out.fail() )
  {
   throw DHException ("Could not open output file for writing..."); 
  }
 }
 
 
 void setIncludeDate( bool inclDate )
 {
  includeDate = inclDate; 
 } 
ostream& operator<<(const string s)
  {
   
   if ( !out.good() )
    {
      throw DHException ("Could not open output file for writing..."); 
    }
    
    if ( includeDate )
    {
       
       out<<Date::getFormattedDate()<<"->";
 }
    
    return out<< s;
    
  }

};
#endif

----------------------------------
I have two questions
1) How do I detect a problem writing to the file after it's open? !out.good() does not seem to catch me deleting the file while the program is running

2) How do I get the file name from out? (In the case where I'm using a file rather than the screen) for use in the exception call?

Maybe I should just have a FileLog class that descends from NewLog? (will be renamed to Log when it's finished..)

>not sure why it needs to be done with this way rather than the doing it inside the {}
Some members cannot be assigned to, and references are among these. Since _out is a reference, by the time you get inside the constructor body, you can no longer initialize it. You should favor an initializer list rather than assignment in the body because it always works even for those troublesome types, and is never less efficient than assignment in the constructor body.

>!out.good() does not seem to catch me deleting the file while the program is running
The current design of your Log suggests that out is always available and valid between the constructor and the destructor. If you're going to assume that the stream is a file stream (a very bad idea if it may not be), you would be better off specializing Log with inheritance.

>2) How do I get the file name from out?
You don't. Consider this instead since what you want is very hard to get right with the current design:

class LogBase {
  bool _includeDate;
public:
  void setIncludeDate(bool inclDate) { _includeDate = inclDate; }
  virtual ostream& operator<<(const string& s) = 0;
};

class Log: public LogBase {
  ostream& _out;
public:
  Log(ostream& out = cout): _out(out) {}
  ostream& operator<<(const string& s) { return _out<< s; }
private:
  Log operator=(const Log&);
};

class FileLog: public LogBase {
  const string _filename;
  ofstream _out;
public:
  FileLog(const string& file): _filename(file), _out(file.c_str())
  {
    if (!_out.is_open())
      throw runtime_error("File open failure");
  }

  ostream& operator<<(const string& s) { return _out<< s; }
private:
  FileLog(const FileLog&);
  FileLog operator=(const FileLog&);
};

Because it seems you want a file log and a non-file log to do fundamentally different things, you can factor out the common parts and place them in a base class, then derive from the base class into specialized classes for generic ostreams and file streams. The difference being that the generic Log will accept an already open stream without assuming anything while the FileLog will maintain an internal file stream of its own.

Thanks again, I will try it out. When finished, I'll post the final code. (I want to write some other methods that can handle printf semantics, etc.)

Narue,

I've attached my Log.cpp and Log.h. I'd be glad to hear your critique - (be gentle..)

What I'm trying to do now is create a class that can handle multiple FileLog's, so that I can do the following syntax and be able to have the MultiLog 'write' to all the logs inside of it (without the caller needing to manage how many logs there are, etc.)..

FileLog fl ("LogOne.txt");
FileLog fl2( "LogTwo.txt" );
 
MulitiLog ml;
ml.add( fl );
ml.add( fl2);
 
ml<<"Write this to both logs"<<endl;
ml<<"blah1"< " blah2"<<endl;

My question is this - how do I write the << operator such that it writes to multiple ofstreams, given that the << operator needs to return an ostream itself? (Meaning if I have the following code in MultiLog )

class MultiLog
{
		 vector<Log*> logs;
	public:
		//etc addLog, other functions.
	 ostream & operator<<(const string &s )
	 {
	 for ( int i=0;i< logs.size();i++)
		{
				*logs[i]<<s;
		}
 
	 //WHAT DO I RETURN HERE? (considering I want to handle endl, etc...)
 
	 }
 
};
Attachments
#include "Log.h"


void ScreenLog::writeLog( Printable &a )
{
	writeLog(a.toString());
}
void ScreenLog::writeLog ( const ostringstream & inputStream)
{
	writeLog(inputStream.str());
}

void ScreenLog::writeLog( const double & logLine )
{
	writeLog ( StringUtils::doubleToString(logLine) );
}


void ScreenLog::writeLog(const char * logLine, ... )
{
			
		 va_list args;
	   va_start (args, logLine);
     char *dan = new char[MAX_LOG_SIZE];
     long count = vsnprintf (dan, MAX_LOG_SIZE, logLine, args);
     va_end (args);
		
	string temp;
	
	
	if ( count == -1 || count > MAX_LOG_SIZE )
	{
		temp=dan;
		temp+="...<LOG TRUNCATED>...";
	}
	else
		temp=dan;
		
	if ( dan !=NULL )
	{
		delete dan;
	}
	writeLog( temp );
	    
}

void ScreenLog::writeLog( string logLine )
{
	if ( includeDate )
		out<<Date::getFormattedDate()<<separator;
		
	out<<logLine;
}


/************************************

Start of FileLog implementation

****************************************/

void FileLog::writeLog( string logLine )
  {
  	
  	if ( !out.is_open() )	
  		throw DHException ("File write failure:" + filename);
  	
  	if ( includeDate )
  		out<<Date::getFormattedDate()<<separator;

	out<<logLine;
  }


void FileLog::writeLog( Printable &a )
{
	writeLog(a.toString());
}
void FileLog::writeLog ( const ostringstream & inputStream)
{
	
	writeLog(inputStream.str());
}

void FileLog::writeLog( const double & logLine )
{
	writeLog( StringUtils::doubleToString( logLine ));
}


void FileLog::writeLog(const char * logLine, ... )
{
			
		 va_list args;
	   va_start (args, logLine);
     char *dan = new char[MAX_LOG_SIZE];
     long count = vsnprintf (dan, MAX_LOG_SIZE, logLine, args);
     va_end (args);
		
	string temp;
	
	
	if ( count == -1 || count > MAX_LOG_SIZE )
	{
		temp=dan;
		temp+="...<LOG TRUNCATED>...";
	}
	else
		temp=dan;
		
	if ( dan !=NULL )
	{
		delete dan;
	}
	writeLog( temp );
	    
}
#include "MainInclude.h"

#ifndef __NEW_LOG__
#define __NEW_LOG__

#define MAX_LOG_SIZE 5000
class Log
{
  protected:
  bool includeDate;
  string filename;
  string separator;
  
  
public:
  void setIncludeDate(bool inclDate) { includeDate = inclDate; }
  void setSeparator ( string sep ) { separator = sep; }
  string getFileName() { return filename; }
  virtual ostream& operator<<(const string& s) = 0;
  virtual void close() = 0;
  virtual void writeLog( string logLine) = 0;
  virtual ostream& operator<<(Printable & s)  = 0;

};

class ScreenLog: public Log
{
  
public:
	ostream& out;
  ScreenLog(ostream& out = cout, string sep = "->"): out(out)
  {
  	filename="CONSOLE";
  	setSeparator( sep );
  	setIncludeDate( false );
  	
  }
  ostream& operator<<(const string& s) 
  { 
  	if ( includeDate )
  		out<<Date::getFormattedDate()<<separator;
  	
  	return out<< s;
  }
  
  
  ostream& operator<<(Printable & s) 
  { 
  	if ( includeDate )
  		out<<Date::getFormattedDate()<<separator;
  	
  	return out<< s.toString();
  }
  
    
  void writeLog( string logLine );
  void writeLog( Printable &a );
  void writeLog ( const ostringstream & inputStream);
  void writeLog( const double & logLine );
  void writeLog(const char * logLine, ... );
  void close() { }
  
  ScreenLog operator=(const ScreenLog& a)
  {
  	return a;
  }
  
};
  
class FileLog: public Log
{
  
  ofstream out;
public:
  FileLog(const string& file, string sep = "->"): out(file.c_str())
  {
  	filename= file;
  	setSeparator( sep );
  	setIncludeDate( false );
  	
    if (!out.is_open())
      throw DHException ("File open failure:" + filename);
  }

  void writeLog( string logLine );
  void writeLog( Printable &a );
  void writeLog ( const ostringstream & inputStream);
  void writeLog( const double & logLine );
  void writeLog(const char * logLine, ... );
  
  ostream& operator<<(const string& s) 
  { 
  	if ( !out.is_open() )	
  		throw DHException ("File write failure:" + filename);
  		
  	if ( includeDate )
  		out<<Date::getFormattedDate()<<separator;
  		
  	return out<< s;
  }
  ostream& operator<<(Printable & s) 
  { 
  	if ( includeDate )
  		out<<Date::getFormattedDate()<<separator;
  	
  	return out<< s.toString();
  }
  void close()
  {
  		out.close();
  }
private:
  FileLog(const FileLog&);
  FileLog operator=(const FileLog&);
};

#endif

>how do I write the << operator such that it writes to multiple ofstreams,
>given that the << operator needs to return an ostream itself?
Did I mention how much I enjoy your insightful questions? You're pushing yourself and the language, and it's inspiring to watch. :)

But, to answer your question, you can't using basic iostreams. The most straightforward solution is to start doing your own thing using similar rules as the standard streams. For example, if returning an ostream& doesn't cut it, redefine operator<< to return a MultiLog&. If you still want to use manipulators, define the controlling operator<< for manipulators to take and return a MultiLog&. Consider this example:

#include <iostream>
#include <string>

using namespace std;

namespace jsw {
  class DualLog {
    ostream& _o1;
    ostream& _o2;
  public:
    DualLog(ostream& o1, ostream& o2)
      : _o1(o1), _o2(o2)
    {};

    // Redefine the no-argument manipulator controller
    DualLog& operator<<(DualLog& (*manip)(DualLog&))
    {
      return manip(*this);
    }

    DualLog& operator<<(const string& s)
    {
      _o1<< s;
      _o2<< s;

      return *this;
    }

    // For proper definition of endl
    void flush()
    {
      _o1.flush();
      _o2.flush();
    }
  };

  // DualLog specialized manipulator
  DualLog& endl(DualLog& log)
  {
    log<<"\n";
    log.flush();

    return log;
  }
}

int main()
{
  jsw::DualLog log(cout, cerr);

  log<<"This is a test"<<jsw::endl;
  log<<"Another test"<<jsw::endl;
  log<<"Last test"<<jsw::endl;
}

The trick is knowing when to follow the rules (almost always), when to bend the rules (such as above), and when to shatter them (very rarely, if ever).

Narue,

The problem is that I don't know how many ostreams (more likely ofstreams) that I will have - I won't necessarily have 2. (So I won't be able to initialize the ofstreams in a constructor).

I want to be able to have unlimited ofstreams, using an addLog( Log * ) type function. I would then iterate through the logs that I have accumulated and call the << or the writeLog( string ) function of each log object. I want to maintain the actual writing to the stream in the Log function as I have some options like including the datetime stamp, etc.

Also, what's this section about? Never saw this (*manip) stuff...

// Redefine the no-argument manipulator controller
DualLog& operator<<(DualLog& (*manip)(DualLog&))
{
return manip(*this);
}

Also, I noticed you are doing "\n" to simulate endl. To be platform independent, I should do some sort of #ifdef on whether I'm on windows to do \r\n? (Does the 'regular' endl work that way?)

>I won't necessarily have 2.
You can have as many as you want. My code is always just an example.

>I want to be able to have unlimited ofstreams
So use a vector of pointers to ostream, or something equivalent:

#include <iostream>
#include <vector>

class A {
  std::vector<std::ostream*> _o;
public:
  void add(std::ostream *o) { _o.push_back(o); }
  std::ostream& operator[](int i) { return *_o[i]; }
};

int main()
{
  A a;

  a.add(&std::cout);
  a.add(&std::cerr);

  a[0]<<"This is a test"<<std::endl;
  a[1]<<"Another test"<<std::endl;
}

>Never saw this (*manip) stuff...
That's because it's hidden from you. What you quoted was a function taking a function as its argument.

>To be platform independent, I should
Do exactly what I did. The character literal '\n' will do the right thing regardless of the platform.

This article has been dead for over six months. Start a new discussion instead.