Jump to content
  • Advertisement
Sign in to follow this  
ChrisPepper1989

Overloading to create cout effect, is it possible?

This topic is 3047 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi Everyone, Im going off on one of my programming tangents and im wondering if anyone can help me figure this out so that i can return to actual work =P. Basically i have a little debug window courtesy of CEGUI and i want to be able to go
gui->Debug << "this is me showing i can type a number: " << 5 << "look i did it!, now for a float" << 0.5f;

so to get started i made myself a debug class and set up operator functions like the following:
void Debug::operator<< (float fl)
{
    OutPutToDebug(ToString<float>(fl));
}
void Debug::operator<< (std::string str)
{
    OutPutToDebug(str);
}

which did let me do the following: gui->Debug << "this is me showing i can type text: "; OR : gui->Debug << 5; then i went about trying to figure out the return type to allow me to do longer ones, so i tried a few but i figured the best option was either std::stringstream or simply adding up strings. this was my atempt at adding up strings:
std::string Debug::operator<<( float fl )
{
	std::string line+=XF::Utilities::ToString<float>(fl);
	return line;
}

std::string Debug::operator<<( std::string fl )
{
	std::string line+=fl;
	return line;
}

and that gave me a whole host of errors like this: Error 2 error C2784: 'std::basic_ostream<_Elem,_Traits> &std::operator <<(std::basic_ostream<_Elem,_Traits> &,const std::_Smanip<_Arg> &)' : could not deduce template argument for 'std::basic_ostream<_Elem,_Traits> &' from 'std::string' c:\documents and settings\chris\desktop\agt\code\project starblazer\gamescreens\menutesting\menutestscreenbuttons.cpp 20 and my atempt at stringstream std::stringstream Debug::operator<< (std::stringstream fl) { return fl; } gave me the errors: Error 3 error C2248: 'std::basic_ios<_Elem,_Traits>::basic_ios' : cannot access private member declared in class 'std::basic_ios<_Elem,_Traits>' c:\program files\microsoft visual studio 9.0\vc\include\sstream 516 im not really sure if im choosing the wrong return type, the wrong structure of the function definition (should i be using const?) or if its simply not possible! Can anyone help me on this one it would be much appreciated! Thanks, Chris

Share this post


Link to post
Share on other sites
Advertisement
you have to return a referance to the output stream not a std::string
in your case you might want to use std::ostringstream. the general look of the function you are trying to create goes a little something like this.

std::ostream& operator << (std::ostream& os, const double x)
{
// -- perform output operations here
return os
}





the referance to the output stream allows this to happen:
os << someval1 << someval2;

otherwise you would only be able to do this:
os << someval1;
os << someval2;

which is generally not what you would want since the whole point of the '<<' and '>>' operators is so you can 'stream' data

Share this post


Link to post
Share on other sites

void Debug::operator<< (float fl)
{
OutPutToDebug(ToString<float>(fl));
}
void Debug::operator<< (std::string str)
{
OutPutToDebug(str);
}



Should become

Debug& Debug::operator<< (float fl)
{
OutPutToDebug(ToString<float>(fl));
return *this;
}
Debug& Debug::operator<< (std::string str)
{
OutPutToDebug(str);
return *this;
}

Share this post


Link to post
Share on other sites
Wow That was quick thank you Steve132 and CodeCriminal, but im afraid each solution returned an error.

Steve132s solution produced the compile error: Error 15 error C2296: '<<' : illegal, left operand has type 'Debug *' c:\documents and settings\chris\desktop\agt\code\project starblazer\gamescreens\menutesting\menutestscreenbuttons.cpp 20

(although i think i have a solution...ill post if im right EDIT: Nope =[)

on the line:

bool MenuTestScreen::UpgradeTower(const CEGUI::EventArgs &args)
{
mGUI->mDebug << "Hello " << 5; //here
return true;
}





but im assuming thats because Debug is not derrived from any of the stream classes???

and CodeCriminal soltion produced this compile error: "Error 22 error C2804: binary 'operator <<' has too many parameters c:\documents and settings\chris\desktop\agt\code\project starblazer\menu\debug.h 44"

this solution ran without compile errors:


std::stringstream& operator << (std::string str)
{
streamLine << str;
OutPutToDebug(streamLine.str());
return streamLine;
}
std::stringstream& operator << (float fl)
{
streamLine << fl;
OutPutToDebug(streamLine.str());
return streamLine;
}




but doesnt work logically, which i thought it might not, OutPutToDebug is inserting a new item into a list box, so in the case of debug << "hello" << 5;
we get a list box item saying hello, then the next it will say hello5 hello, and keep adding on.

is there anyway i can know that its the last input, so i would know its just entered debug, and i can clear the string stream and output the whole line?
as it stands here is all my code (each persons version was ran on its own, other versions were commented out for each build):


/*************************************
Author: Christopher Pepper 11/12/2009
***************************************/

#pragma once

#include <ostream>
#include "CEGUI/CEGUIWindowManager.h"
#include <CEGUI/CEGUI.h>
#include "OgreVector3.h"
#include <iosfwd>

#define DEFAULT_CEGUI_SKIN "TaharezLook"
#define DEFAULT_CEGUI_FONT "BlueHighway-10.font"

class Debug
{
private:
bool mInverted;
bool mEnabled;
int mKeepThreshold;
CEGUI::Listbox* mDebugBox;



CEGUI::WindowManager *mWindowManager;

std::stringstream streamLine;
public:


//My updated version
std::stringstream& operator << (std::string str)
{
streamLine << str;
OutPutToDebug(streamLine.str());
return streamLine;
}
std::stringstream& operator << (float fl)
{
streamLine << fl;
OutPutToDebug(streamLine.str());
return streamLine;
}
//codecriminals version
std::ostream& operator << (std::ostream& os, const double x)
{
// -- perform output operations here
std::stringstream streamLine;
streamLine << os;
OutPutToDebug(streamLine.str());
return os;
}
//Steve132s version
Debug* operator << (std::string str)
{
OutPutToDebug(str);
return this;
}
Debug*operator << (float fl)
{
OutPutToDebug(XF::Utilities::ToString<float>(fl));
return this;
}



void DisableDebug(){mWindowManager->getWindow("Debug")->setEnabled(false);};
void EnableDebug(){mWindowManager->getWindow("Debug")->setEnabled(true);};
void HideDebug(){mWindowManager->getWindow("Debug")->setVisible(false);};
void ShowDebug(){mWindowManager->getWindow("Debug")->setVisible(true);};
void Initialize(std::string skin = DEFAULT_CEGUI_SKIN, std::string WindowTag = "Root");

void OutPutToDebug(std::string output);
/*How many items will the debug store in the debug listbox (please note that the app
will slow down at high numbers)*/

void SetKeepThreshold(float number){mKeepThreshold=number;};
#ifdef _DEBUG
Debug() : mInverted(true) , mEnabled(true) , mKeepThreshold(300){};
#elif
Debug() : mInverted(false) , mEnabled(false), mKeepThreshold(0){};
#endif
~Debug();
};





.cpp :


#include "Debug.h"
#include "Utilities.h"
#include "Logger.h"
#include "OgreVector3.h"



void Debug::OutPutToDebug( std::string output )
{
if(!mDebugBox)
{
XF::Logger::GetInstance()->Log("Error Debug Not Initialized");
throw "Error Debug Not Initialized";
}
if(mEnabled)
{
if(mDebugBox->getItemCount() > mKeepThreshold)
{
if(mInverted)
{
mDebugBox->getListboxItemFromIndex(mKeepThreshold)->setAutoDeleted(true);
mDebugBox->removeItem(mDebugBox->getListboxItemFromIndex(mKeepThreshold));
}
else
{
mDebugBox->removeItem(0);
mDebugBox->getListboxItemFromIndex(0)->setAutoDeleted(true);
mDebugBox->removeItem(mDebugBox->getListboxItemFromIndex(0));
}


}

CEGUI::ListboxTextItem* lItem = new CEGUI::ListboxTextItem(output);
if(mInverted)
mDebugBox->insertItem(lItem,0);
else
mDebugBox->addItem(lItem);

}


}
void Debug::Initialize(std::string skin , std::string WindowTag )
{
mWindowManager = NULL;
std::string buttonSkinType =skin/*+"skin"*/+"/Listbox";
mWindowManager = CEGUI::WindowManager::getSingletonPtr();
mDebugBox = (CEGUI::Listbox*)CEGUI::WindowManager::getSingletonPtr()->createWindow(buttonSkinType, "Debug");

mDebugBox->setSize(CEGUI::UVector2(CEGUI::UDim(1, 0), CEGUI::UDim(0.15, 0)));
mDebugBox->setPosition(CEGUI::UVector2(CEGUI::UDim(0, 0), CEGUI::UDim(0,0)));
mDebugBox->setAlpha(0.5);

mWindowManager->getWindow(WindowTag)->addChildWindow("Debug");
}
Debug::~Debug()
{
mWindowManager->destroyWindow("Debug");
}





Share this post


Link to post
Share on other sites
Quote:

Wow That was quick thank you Steve132 and CodeCriminal, but im afraid each solution returned an error.

Steve132s solution produced the compile error: Error 15 error C2296: '<<' : illegal, left operand has type 'Debug *' c:\documents and settings\chris\desktop\agt\code\project

They should be non-member functions.

Share this post


Link to post
Share on other sites
Thank you jpetrie and SiCrane


Debug& operator << (std::string str)
{
OutPutToDebug(str);
return *this;
}
Debug&operator << (float fl)
{
OutPutToDebug(XF::Utilities::ToString<float>(fl));
return *this;
}




the code above compiled and ran! but there is still the problem of the logical error of OutPutToDebug, as it posts to a list box via items (at the moment) so "hello" << 5;
will print

hello
5

when i would prefer it on one line.

is there anyway i can tell when the stream has completed? so that i can then print the full line? or when its first called so that i can tell it to start a new line?

or is this not actually possible? and i should take more of a buffered aproach, print if line is not empty on the draw loop type thing

Thanks for all the support guys!

Share this post


Link to post
Share on other sites
Yey I got the solution i wanted, i now have a very nice litle debug working with CEGUI list box, probably not the best solution in the world but it'l do i updated my header code to have the following:


private:
*--detail omitted--*
std::stringstream streamLine;
bool mStringEmpty;
public:

*--detail omitted--*
Debug& operator << (std::string str)
{
streamLine << str;
mStringEmpty = false;
return *this;
}
Debug&operator << (float fl)
{
streamLine << fl;
mStringEmpty = false;
return *this;
}






and added the following to be ran every frame:

void Debug::Update()
{
if(!mStringEmpty)
{
OutPutToDebug(streamLine.str());
streamLine.str("");
mStringEmpty = true;
}
}





im not to keen on the every frame bit or the face im not really using the stream operator itself for what its meant for..but it does produce the desired result! if anyone can offer improvements it would be much appreciated!

is there anyway to know if its the first/laststream? my next challenge is to make it so
mDebug << "hello " << 5;
mDebug << "hello im on a new line " << 4;
prints:
hello 5
hello im on a new line 4
so the first/last thing would still be usefull, but im sure ill thing of something!

*EDIT: For now im happy to use mDebug << "hello " << 5 << D_ENDL;
D_ENDL being an int (98765) that i hopefully wont ever want to print :P
with this change to the float version:
Debug&operator << (float fl)
{
mStringEmpty = false;

if((int)fl == D_ENDL)
{
Update();
}
else
{
streamLine << fl;
}

return *this;
}
*

Thanks to everyone who posted! I would not have got to this solution without u guys but even more importantly you taught me stuff about streams that i can use again in the future to better effect! i haz left u alz good feedback =]

[Edited by - ChrisPepper1989 on February 18, 2010 6:15:52 PM]

Share this post


Link to post
Share on other sites
You really should try to understand what you're doing. :)

When you write something like std::cout << {value of type T}, it calls an overload for the operator<<. Notice the types of the arguments: operator<< always requires exactly two arguments (because it's a binary operator). We can implement the overload either as a free function with two parameters (left-hand side, then right-hand side), or as a member function with one (the this-object represents the left-hand side, and the single parameter represents the right-hand side).

Of course, an operator overload is really just a function with a special name that is called in a special way. As such, it has a return value as well. If you write something like foo << bar << baz, the operators are applied left-to-right (operator<< has left-to-right associativity), so foo << bar gets evaluated first. Thus, the whole thing gets translated into operator<<(operator<<(foo, bar), baz), or alternatively foo.operator<<(bar).operator<<(baz), or some combination. :)

For that to work, the first application has to return something that will work with the second application. The way std::ostream (the type of the std::cout object) handles this is to return another std::ostream. That way, you can chain together as much as you'd like: each application returns a std::ostream that's suitable for the next application of the operator. Of course, it can't just return any old ostream; it has to return itself (because you want all the output to go to the same place). It can't even return a copy of itself (ostreams can't even be copied, in the first place), so it returns itself, by reference.

In your case, you want to chain with the Debug object, so you return a Debug&, via 'return *this', as in Steve132's example.




As for the question of how to put multiple things on the same output line, you have some options.

1) You can modify OutPutToDebug() so that it looks for newlines in the strings being logged and breaks up the text accordingly, and otherwise appends the new text to the current line.

2) You can modify OutPutToDebug() so that it always appends the new text to the current line, and then special-case operator<< for std::endl such that it goes to the next line in the GUI display. (Hint: the type of std::endl is std::ostream&(*)(std::ostream&). But it's not the only object of that type, so you'll want to check for equality as well.) This is kind of a hack, though.

3) My preferred solution: you can separate out the concept of a debug message from a debug display. That is, make a separate class called DebugMessage that holds the std::stringstream instance, in which you accumulate the text with operator<< overloads. Tell it about a corresponding Debug instance when you create it, and in the DebugMessage destructor, you have it "flush" to the Debug instance by caling OutPutToDebug().

That looks like:


// Debug has no operator overloads, but still has OutPutToDebug as before

struct DebugMessage {
std::stringstream buffer;
Debug* display;

DebugMessage(Debug& debug): display(&debug) {}
template <typename T>
DebugMessage& operator<<(const T& t) { buffer << t; return *this; }
~DebugMessage() { display->OutPutToDebug(buffer.str()); }
};

// Used like:
DebugMessage(gui->debug) << "test: " << 5 << " is bigger than " << 4.2;

// See that? We construct an unnamed instance of DebugMessage, use operator
// chaining to build up the message, and then at the end of the statement the
// instance falls out of scope, so the destructor is called, triggering output.

// This only works with the member version of the operator<< overload,
// because it relies on modifying a temporary value. Otherwise you'd have to
// declare a variable to hold an instance of DebugMessage, and manage its
// lifetime, e.g.
// {
// DebugMessage dm(gui->debug);
// dm << "test: " << 5 << " is bigger than " << 4.2;
// } <-- note the braces!




This technique exhibits several interesting ideas for good design, and also avoids separately converting each item with ToString(). (By the way, I hope you didn't write that yourself; you can just use boost::lexical_cast if you need it.)

[Edited by - Zahlman on February 18, 2010 7:55:14 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
You really should try to understand what you're doing. :)

indeed that is always helpful lol!

Quote:
Original post by Zahlman
When you write something like std::cout << {value of type T}, it calls an overload for the operator<< . Notice the types of the arguments: operator<<; always requires exactly two arguments (because it's a binary operator). We can implement the overload either as a free function with two parameters (left-hand side, then right-hand side), or as a member function with one (the this-object represents the left-hand side, and the single parameter represents the right-hand side).

riiight this actually helps alot, ive been messing around with overloading operators before and always when the 2 arguments was appropriate, that is a major penny drop! and a bit of a DOI!

Quote:
Original post by Zahlman
If you write something like foo << bar << baz, the operators are applied left-to-right (operator<< has left-to-right associativity), so <<foo << bar gets evaluated first. Thus, the whole thing gets translated into operator<<(operator<<(foo, bar), baz), or alternatively foo.operator<<(bar).operator<<(baz), or some combination. :)

For that to work, the first application has to return something that will work with the second application. The way std::ostream (the type of the std::cout object) handles this is to return another std::ostream. That way, you can chain together as much as you'd like: each application returns a std::ostream that's suitable for the next application of the operator. Of course, it can't just return any old ostream; it has to return itself (because you want all the output to go to the same place). It can't even return a copy of itself (ostreams can't even be copied, in the first place), so it returns itself, by reference.

Ok that clears all that up, i was stabbing in the dark a little, i should have observed the logical! thats really helpful



Quote:
Original post by Zahlman

1) You can modify OutPutToDebug() so that it looks for newlines in the strings being logged and breaks up the text accordingly, and otherwise appends the new text to the current line.


2) You can modify OutPutToDebug() so that it always appends the new text to the current line, and then special-case operator<< for std::endl such that it goes to the next line in the GUI display. (Hint: the type of std::endl is std::ostream&(*)(std::ostream&). But it's not the only object of that type, so you'll want to check for equality as well.) This is kind of a hack, though.

this is what i was sort of going for...especiatly 2 with more so of a hack but...

Quote:
Original post by Zahlman



// Debug has no operator overloads, but still has OutPutToDebug as before

struct DebugMessage {
std::stringstream buffer;
Debug* display;

DebugMessage(Debug& debug): display(&debug) {}
template <typename T>
DebugMessage& operator<<(const T& t) { buffer << t; return *this; }
~DebugMessage() { display->OutPutToDebug(buffer.str()); }
};

// Used like:
DebugMessage(gui->debug) << "test: " << 5 << " is bigger than " << 4.2;

// See that? We construct an unnamed instance of DebugMessage, use operator
// chaining to build up the message, and then at the end of the statement the
// instance falls out of scope, so the destructor is called, triggering output.


This technique exhibits several interesting ideas for good design.


I really like this, its neat, interfaced, re-usable and just downright clever, I assume then when things are cleaned up they are cleaned up in order so if i was to go:


void main()
{
int a;
int b;
int c;
DebugMessage bob(gui) << "hello";
DebugMessage fred(gui) << "again";

//under the hood
clean up a;
clean up b;
clean up c;
delete bob;
delete fred;
}

cause if that isnt the case its the only thing that i can see that would stop this from working...

Quote:
and also avoids separately converting each item with ToString(). (By the way, I hope you didn't write that yourself; you can just use boost::lexical_cast if you need it.)


Well its actually a joint project and my code collegue wrote the ToString function(it uses stringstream)...2bh I generally avoid boost, not out of snubbery but because up untill now my university has frowned on using external stuff, and generally marks more when they are avoided (which is slightly annoying, as gamedev generally teaches me to use the best tools available and dont re-invent the wheel), plus im pretty sure it would be a nightmare as their computers have deep freeze and stuff so re-setting-up and stuff...but otherwise i would probably by now have alot of helper libraries like boost set up as i've had my fare share of writing home rolled: containers, multi-array, conversions...and so on.

Thank you so much for this detailed explanation i feel this is a post i will re-visit many times more to brush up on that particular operator, i may even post up the whole experience on my website in the form of a small tutorial..

Thanks Again, Chris

p.s. I just lost the game thanks to ur signature =[ lol although i do love it =]

[Edited by - Zahlman on February 19, 2010 11:24:39 AM]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!