• Writing a c++ class that can output stuff using the << operator?
    18 replies, posted
I want to make a log class that can either log to console OR file, depending on the way its constructed etc. I have currently made a template function that does logging for me, but its highly impractical since appending values to a string so it outputs them both in the same log entry is basically impossible unless implemented with some extra trouble Therefore I am looking for a way to output stuff the same way someone would output something to cout. Here is some pseudo-code to explain what I mean [CODE] Loggerclass log; float someFloat = 12.0f; int someInt = 10; log << "Some string" << someFloat << " Some other string:" << someInt; [/CODE] However its really hard to find a proper operator signature for this online and I cant figure it out on my own (yet). Please help me figure this out
Overload operator<< like any other. There are plenty of tutorials on operator overloading.
You could stuff your loggerclass into a struct, then defining the operator values your self in the struct header. [code] // in Logger.h #pragma once struct Log { public: // operator definitions bool operator<<(const /*rightside of operation*/) const { //how your struct handles the << operator here } //content of struct int SampleInt[21] [/code] There won't be a super simple way to handle this unfortunately, you're asking for something that takes some effort. But you could then handle all that code into the nifty operator definition, which would make all future implementations pretty looking. To truly help you more I'll need to know how exactly you plan to be outputting stuff here. Like are you wanting log << string << value << string << value to output the same set of strings every time? Are they outputting them to the console? Are they outputting them to somewhere else? These are kinda what's important. Because if you're just going to cout it all anyway, then your log could be a simple container of a log struct that contains a float and an int or whatever, and then just: [code] cout << "string1" << Log.integer << "string2" << Log.float; [/code] Also appending to a string isn't terribly hard, there's plenty of standard ways to do it actually idk why you're saying it's basically impossible. In std strings can just be appended together via += operator or even by .append() if you want to write it out. Converting ints, floats, etc to strings isn't super hard either, just takes a tiny bit of work making some quick functions.
Don't forget to return a reference to your object, otherwise you won't be able to chain the operators. [code] Loggerclass& operator<<(Loggerclass l, std::string str) { /* stuff */ return l; } // OR Loggerclass& Loggerclass::operator<<(std::string str) { /* stuff */ return *this; } [/code] You'll have to overload it for every type you want to support, or if you just pass the stuff to something else you could template it (templates must be in the header file). For exemple [code] class Logger { public: /* ... */ template<typename T> Logger& operator<<(T t) { /* additional stuff, maybe duplicate the output, print a timestamp before */ out << t; return *this; } private: std::ostream out; // Some output stream like a file } [/code]
[QUOTE=F.X Clampazzo;52848947]You could stuff your loggerclass into a struct, then defining the operator values your self in the struct header. [code] // in Logger.h #pragma once struct Log { public: // operator definitions bool operator<<(const /*rightside of operation*/) const { //how your struct handles the << operator here } //content of struct int SampleInt[21] [/code] There won't be a super simple way to handle this unfortunately, you're asking for something that takes some effort. But you could then handle all that code into the nifty operator definition, which would make all future implementations pretty looking. To truly help you more I'll need to know how exactly you plan to be outputting stuff here. Like are you wanting log << string << value << string << value to output the same set of strings every time? Are they outputting them to the console? Are they outputting them to somewhere else? These are kinda what's important. Because if you're just going to cout it all anyway, then your log could be a simple container of a log struct that contains a float and an int or whatever, and then just: [code] cout << "string1" << Log.integer << "string2" << Log.float; [/code] Also appending to a string isn't terribly hard, there's plenty of standard ways to do it actually idk why you're saying it's basically impossible. In std strings can just be appended together via += operator or even by .append() if you want to write it out. Converting ints, floats, etc to strings isn't super hard either, just takes a tiny bit of work making some quick functions.[/QUOTE] ---Snip cause I realised how dumb that was--- Also I meant it would be hard to append them in ONE COMMAND and ONE OUTPUT with the way I am currently doing it xd. This is how it currently looks: [CODE] loginstance.log<int>(intVal); //-> makes a new entry with the int as a string like this (Entry at FRAMENUM -> intVal) //However if id like to output a string with a value in it it would require the user to append the string toghether manually and I dont like that. [/CODE] I know I could make it so it takes a unlimited number of parameters and template parameters but that would be less intuitive than just using the << operator like most people are used to.
[QUOTE=MoustacheSpy;52849004]Isnt it that normally youd overload an << operator so it gets the ostream of the already stringed togheter stuff and add yours to it? so if youd had this: cout << "hello from" << object1 << "!"<<endl; The overload for the object1 operator would get this already done <<"!"<<endl" and all it has to do is do this: newstream<<object1.member<<otherstream. So all I had to do in my case is make a operator overload that gets the old stream and just kinda outputs it?[/QUOTE] If you want to be able to call the stream's operator<< you'll need to overload its operator so that it accepts your object, but what you're asking is for your class to support the operator<<. To simplify: [code] x << "stuff"; // is equivalent to x.operator<<("stuff"); // OR (depending on what's available) operator<<(x, "stuff"); [/code] Be careful not to have both defined, or you'll get an ambiguous call that won't compile if you simply use x << "stuff"
[quote]So all I had to do in my case is make a operator overload that gets the old stream and just kinda outputs it?[/quote] Yeah basically.
[QUOTE=Niverton;52849029]If you want to be able to call the stream's operator<< you'll need to overload its operator so that it accepts your object, but what you're asking is for your class to support the operator<<. To simplify: [code] x << "stuff"; // is equivalent to x.operator<<("stuff"); // OR (depending on what's available) operator<<(x, "stuff"); [/code] Be careful not to have both defined, or you'll get an ambiguous call that won't compile if you simply use x << "stuff"[/QUOTE] Yeah I actually realised that what I wrote didnt make much sense. However you were a little too fast for me to snip it first xd. Thanks anyways;) I still dont know how to properly overload it tho. How can I use a template with the operator as you specified (I mean when actually using the class not in the implementation) EDIT: I am sorry if I am being even dumber than usual but I tried your solution like below and (unless I did it wrong) it doesnt work as soon as I string more than one thing togheter. [CODE] template <class t> void operator<<(t os){ if(this->loggingMode==logger::CONSOLE){ std::cout<<os<<std::endl; } else{ this->logFile<<"entry on "<<getCurTime()<<"\t->"<<os<<std::endl; } } [/CODE] EDIT (again cause I am forgetful) Here is the compiler error [CODE]error: invalid operands of types "void" and "const char [16" to binary operator <<[/CODE] It happens when I use it like this: [CODE] errorLog<<"Hello my "<<"friend! I have "<<122<<" video games in my steam library"; [/CODE] Well it always happen when I string togheter two or more variables. The error message changes slightly depending on what my last variable is.
[QUOTE=MoustacheSpy;52849046]Yeah I actually realised that what I wrote didnt make much sense. However you were a little too fast for me to snip it first xd. Thanks anyways;) I still dont know how to properly overload it tho. How can I use a template with the operator as you specified (I mean when actually using the class not in the implementation)[/QUOTE] You use it like operator<< [code] Logger log; log << "stuff" << 45 << 0xDEADBEEF; [/code]
[QUOTE=Niverton;52849070]You use it like operator<< [code] Logger log; log << "stuff" << 45 << 0xDEADBEEF; [/code][/QUOTE] Sorry for being such a dumbass btw. I do get these things normally. I added some stuff to my last post while you were posting again. Please tell me what I did wrong again (I know I am really hard to work with. Sorry again)
Slightly modifying a previous answer [QUOTE=Niverton;52848997]Don't forget to return a reference to your object, otherwise you won't be able to chain the operators. [code] Loggerclass& operator<<(std::string str) { /* stuff */ return *this; } [/code] [/QUOTE]
[QUOTE=Niverton;52849092]Quoting myself[/QUOTE] My god... I am so sorry... Well thanks it works now. Sorry for being such a waste of time ;) Just wondering: Why is a reference to the object needed?
You return a reference to the object so you can daisy chain the << commands with multiple objects, just like how you do normally with cout. For example: [code]using namespace std; cout << "This " << "is " << "a string" << endl; // after string "This" is outputted, the above resolves to: cout << "is " << "a string" << endl; // then resolves to: cout << "a string" << endl; // then to: cout << endl; // then finally finishes.[/code] To explain it in more layman's terms, it takes the first two bits of the long chain, does whatever it needs to do with them, then returns a reference to the same initial object (in this case cout) so it can continue the chain.
Didn't read everything but don't use endl unless you really want to flush. Use newline directly.
[QUOTE=WTF Nuke;52849524]Didn't read everything but don't use endl unless you really want to flush. Use newline directly.[/QUOTE] [video]https://youtu.be/6WeEMlmrfOI[/video] Derailing a little but it's important.
[QUOTE=Niverton;52850719][video]https://youtu.be/6WeEMlmrfOI[/video] Derailing a little but it's important.[/QUOTE] Speaking about endl I am having a problem with endl. If I use it at the end I get a compiler error: [CODE] no match for operator << (operand types are "logger" and "<unresolved overload...>" (the last part is referring to endl) [/CODE] as you can see its the same error as before but it only appears at endls. Let me post my code one last time: [CODE] template<class t> logger& operator<<(t os){ if(this->loggingMode==logger::CONSOLE){ std::cout<<os<<std::endl; } else{ this->logFile<<"entry on "<<getCurTime()<<"\t->"<<os<<std::endl; } return *this; } [/CODE] ---------------------------- Problem nr 2. ---------------------------- This is less of a problem but rather a cosmetic problem. If I use the code above with the following usage: [CODE]log<<"This is a single"<<" log"<< "please do respect that"[/CODE] and I log to a file (here the "entry on" stuff is added at the front of the entry) the file doesnt look like this [CODE] entry on (time) -> This is a single log please do respect that. [/CODE] **BUT** like this [CODE] Entry on (time) -> This is a single Entry on (time) -> log Entry on (time) -> please do respect that [/CODE] This happens because the way the program resolves the stringed togheter operators ( as you explained before ). So my question is: What is the easiest method of getting everything into ONE line? (still using the operator). I have one approach, however it requires one additional step from the user, which is something I do NOT want. But here it is anyways: [CODE] 1. Everytime the << operator gets called you add something to a buffer 2. As soon as a new function called send() is called you send the entry as one Problem: User is required to make an additional function call after every entry he wants to make (bad) [/CODE] Is there a good method to replace the one I just described (I am using it currently as a temporary solution, however it is a PAIN)
First off, it's bad practice to use std::endl. As explained in the video I posted above, std::endl does two things: it adds a new line character ('\n'), then flushes the stream (for std::cout, it forces a write to the standard output, for a std::fstream it writes the file on the disk). That's confusing, because you'd expect it to only append a new line, but it's also costly: flushing is a system call that write the output, if you do it often it does slow down your program quiet a bit. You should just append the new line character yourself, then flush the stream when you need to. [B]1) [/B] That's because of the way std::endl is implemented, it expects a std::ostream, but you're using it on your own object. You'll have to write your own manipulator: [code] logger& nl(logger& l) { return l << '\n'; //No flush } //use it like this log << nl; [/code] [B]2)[/B] That's your fault, you're appending std::endl after your object in your code, so yeah that's a new line (and a flush).
[QUOTE=Niverton;52852426]First off, it's bad practice to use std::endl. As explained in the video I posted above, std::endl does two things: it adds a new line character ('\n'), then flushes the stream (for std::cout, it forces a write to the standard output, for a std::fstream it writes the file on the disk). That's confusing, because you'd expect it to only append a new line, but it's also costly: flushing is a system call that write the output, if you do it often it does slow down your program quiet a bit. You should just append the new line character yourself, then flush the stream when you need to. [B]1) [/B] That's because of the way std::endl is implemented, it expects a std::ostream, but you're using it on your own object. You'll have to write your own manipulator: [code] logger& nl(logger& l) { return l << '\n'; //No flush } //use it like this log << nl; [/code] [B]2)[/B] That's your fault, you're appending std::endl after your object in your code, so yeah that's a new line (and a flush).[/QUOTE] I just want to thank everyone involved in this topic. It really tought me quite a bit and I am happy that it turned out the way it did. However I am aware I wasted a bunch of everyones time so I gotta apologize. I am very grateful for everyones help, especially Niverton who posted the most and very quickly at that. This forum is great and the people here too. Hope to meet more of you soon!
[QUOTE=MoustacheSpy;52884824]I just want to thank everyone involved in this topic. It really tought me quite a bit and I am happy that it turned out the way it did. However I am aware I wasted a bunch of everyones time so I gotta apologize. I am very grateful for everyones help, especially Niverton who posted the most and very quickly at that. This forum is great and the people here too. Hope to meet more of you soon![/QUOTE] There's no real need to apologize, C++ is fuckin hard because it's so feature dense and has a lot of legacy stuff to deal with. Stream operators and the stream classes are one of the worst: lots of texts use things like std::endl, for example, and don't tell you why. try to read something like "C++ Primer", in whatever edition is the latest. it features lots of useful stuff and was written for C++11 which is a good start to more modern C++ idioms protip: libgen.io
Sorry, you need to Log In to post a reply to this thread.