1

I have implemented a logging class TLogFile and now I want to overload the output operator<<. I want to use the log like this:

TLogFile* log = new TLogFile("some arguments...");
*log << "Hello world."; // (1)
*log << "Hello world." << endl; // (2)
*log << std::hex << setw(2) << setfill('0') << someValue << endl; // (3)

I used ostream as a class member and as a friend. The class looks like this:

namespace app {

class TLogFile
{
public:
    app::TLogFile& operator<< (std::string& out);
    std::ostream&  operator<< (std::ostream& out);
    friend std::ostream& operator<< (std::ostream& out, TLogFile& o);
};

} // namespace app

Only plain text (1) is working by using the string version. A soon as I use endl (2) or iomanip (3) I get error messages:

../src/main.cpp:164:70: error: no match for 'operator<<' in 'sysdat.app::cSystemData::obj.app::cSystemObjects::applicationLog->app::TLogFile::operator<<((* & std::basic_string(((const char*)"sysdat.obj.applicationLog <<"), ((const std::allocator*)(& std::allocator()))))) << std::endl' ../src/main.cpp:164:70: note: candidates are: ../src/inc/logger.h:85:17: note: app::TLogFile& app::TLogFile::operator<<(const string&) ../src/inc/logger.h:85:17: note: no known conversion for argument 1 from '' to 'const string& {aka const std::basic_string&}' ../src/inc/logger.h:88:17: note: std::ostream& app::TLogFile::operator<<(std::ostream&) ../src/inc/logger.h:88:17: note: no known conversion for argument 1 from '' to 'std::ostream& {aka std::basic_ostream&}' ../src/inc/logger.h:93:23: note: std::ostream& app::operator<<(std::ostream&, app::TLogFile&) ../src/inc/logger.h:93:23: note: no known conversion for argument 1 from 'app::TLogFile' to 'std::ostream& {aka std::basic_ostream&}'

I believed that one of the ostream version should work. Has anyone an idea how to overload the operator so that endl and iomanip can be used?

tangram67
  • 59
  • 1
  • 6

2 Answers2

0

Your operator<< is able to take only std::ostream& and std::string&
(note: probably it should be const std::string&).

The most elegant solution I can imagine is to write a template:

class TLogFile{
protected:
    std::ostream* stream;
public:
    /* default ctor, copy ctor and assignment operator: */
    TLogFile(std::ostream& _stream=std::clog):stream(&_stream){}
    TLogFile (const TLogFile&) =default;
    TLogFile& operator= (const TLogFile&) =default;

    /* std::endl is overloaded,
     * so I think compiler doesn't know which version to use.
     * This funchtion handles function pointers, including std::endl
     */
    inline TLogFile& operator<< (std::ostream&(*func)(std::ostream&)){
        (*stream) << func;
        return *this;
    }        

    /* should handle everything else */
    template<typename T>
    inline TLogFile& operator<< (const T& t) {
        (*stream) << t;
        return *this;
    }
}

See it working in online compiler

This way your objects' operator<<s should be able to take anything that std::ostream's can take.

Edit:

Next time, please say that you want to have custom std::endl.

I'm not sure that function with signature

inline TLogFile& operator<< (std::ostream&(*func)(std::ostream&))

is used only when std::endl is passed to it. My previous solution seems inelegent or even inworking. I'm wondering about how to change behaviour of std::endl when it's passed to object of different class.

Notes:

  1. In most cases I'd like to use '\n instead of std::endl.

  2. TLogFile* log = new TLogFile("some arguments...");
    

    I think using raw pointer isn't the best idea here (it's easy to forget about delete),
    unless you have to explicitly decide when the object should die.

    • When the object should die when the current scope does, it should be a local variable:

      TLogFile log("some arguments...");
      //Usage:
      log << "Hello world."; // (1)
      log << "Hello world." << endl; // (2)
      log << std::hex << setw(2) << setfill('0') << someValue << endl; // (3)
      
    • If the object is used in multiple places, and each of the places uses it independently from others, IMO the best solution is to use std::shared_ptr:

      #include <memory>
      #include <utility>
      auto log=std::make_shared<TLogFile>("some arguments...");
      //Usage:
      *log << "Hello world."; // (1)
      *log << "Hello world." << endl; // (2)
      *log << std::hex << setw(2) << setfill('0') << someValue << endl; // (3)
      

      This way the object dies when the last shared_ptr does.

  3. I used pointer in the class to be able to re-assign it. If you don't need re-assignment, you can use reference instead.

Community
  • 1
  • 1
GingerPlusPlus
  • 5,336
  • 1
  • 29
  • 52
  • I wonder what the hell the line does: new TlogFile(whatever) returns a TLogFile* --- Sorry, extracted it from the real source with a mistake, now correct in the original post. – tangram67 Oct 19 '14 at 12:17
  • Thanks for the hint with the tamplate, I will try that and come back with the result. – tangram67 Oct 19 '14 at 12:20
  • I removed all operators apart from the template. It's working as log as there is no endl or iomanip. I got the following errro: ../src/main.cpp:164:70: error: no match for 'operator<<' in 'sysdat.app::cSystemData::obj.app::cSystemObjects::applicationLog->app::TLogFile::operator<< [with T = char [30], app::TLogFile = app::TLogFile]((*"*sysdat.obj.applicationLog <<")) << std::endl' The template is more elegant. But the problem with the iomanip still persists... – tangram67 Oct 19 '14 at 12:44
  • Why you implement our own class wrapping `std::ostream`? – GingerPlusPlus Oct 19 '14 at 12:45
  • You mean the logging class should be derived from ostream? – tangram67 Oct 19 '14 at 12:49
  • I don't know if it should, but I know this is alternative approach. – GingerPlusPlus Oct 19 '14 at 12:50
  • [`std::endl`](http://www.cplusplus.com/reference/ostream/endl/) is ambiguous, and compiler doesn't know which to choose; I have a code fixing it, I'm posting it in a minute. – GingerPlusPlus Oct 19 '14 at 13:03
  • [This site](http://www.cplusplus.com/reference/ostream/endl/) says that `std::endl` is overloaded. Probably compiler can't choose which version to use. – GingerPlusPlus Oct 19 '14 at 13:12
  • OK, it looks very good now! This seems to solve the problem for the output to an ostream. I have given an abstract example of my code. In the real code I'm not writing directly to the ostream. I have a writer method that is doing several things. I changed the ostream in your solution to stringstream and the format is working in general. – tangram67 Oct 19 '14 at 14:11
  • The template ist called for each <<. If I put my writer method in the template funtion I get one logged line for each call. It looks like this: 2014-10-19 16:02:29.268 : Buffer: size= 2014-10-19 16:02:29.268 : Buffer: size=256 2014-10-19 16:02:29.268 : Buffer: size=256 capacity= 2014-10-19 16:02:29.268 : Buffer: size=256 capacity=1024 2014-10-19 16:02:29.268 : Buffer: size=256 capacity=1024 I'm missing the exact moment to call my writer method. – tangram67 Oct 19 '14 at 14:13
  • Is there a way to check if endl is in the stream? – tangram67 Oct 19 '14 at 14:14
0

Thanks to GingerPlusPlus. I found out, that the operator operator<< (std::ostream&(*func)(std::ostream&)) is called only once for the endl (maybe this assumtion is not always true, Please read remarks/edit above of GingerPlusPlus). I replaced the ostream against a stringstream and write the contens of the stringstream when the operator ist called.

class TLogFile{
protected:
    std::ostream* stream;
    std::stringstream line;
public:
    /* default ctor, copy ctor and assignment operator: */
    TLogFile(std::ostream& _stream=std::clog):stream(&_stream){}
    TLogFile (const TLogFile&) =default;
    TLogFile& operator= (const TLogFile&) =default;

    void write() {
        // Doing some write stuff
        // ...

        // Empty stringstream buffer
        line.str(std::string());
    }

    /* std::endl is overloaded,
     * so I think compiler doesn't know which version to use.
     * This funchtion handles function pointers, including std::endl
     */
    inline TLogFile& operator<< (std::ostream&(*func)(std::ostream&)){
        line << func;
        write();
        return *this;
    }        

    /* should handle everything else */
    template<typename T>
    inline TLogFile& operator<< (const T& t) {
        line << t;
        return *this;
    }


}
tangram67
  • 59
  • 1
  • 6