Overloading operator "put-to" (<<)

I don't do much operator overloading so I'm not sure if I'm on the right track. The dream is to make a logging function that can be used as such:
MyLogger(0xff)<<"text"<<numbers<<std::endl;
where 0xff is a mask that sets some options.

For now I will settle for:
1
2
MyLogger.setMask(0xff);
MyLogger<<"text"<<numbers<<std::endl;


The question is:
How can I overload the "<<" operator so that I can input to a class?

Here is an example that I found online for overloading the "<<" operator. This one takes a class and then puts it to an ostream object:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Date
{
	friend ostream &operator << (ostream &, Date &);

public:
	int day, month, year;

	ostream &operator << (ostream &output, const Date &d)
	{
		char *MonthName[13] = {"","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
		output << d.day << "/" << MonthName[d.month] << "/" << d.year;
		return (output)
	}
};


So here I gather that the "<<" operator has 2 arguments, the left side and the right side of the operator itself. I'm not sure what the return type is really for and I don't really know what the friend keyword is for. If I ignore these and use this philosophy I come up with:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ofstream fout("LogFile.txt");
class MyLogger
{
public:
	short mask;
	MyLogger()	{	mask = 0;	}
	~MyLogger()	{	fout.close();	}

	void setMask(short mask_in)	{ mask = mask_in; }

	void &operator << (MyLogger &output, string &input)
	{
		if (mask & 0xf0)
		{
			fout << input;
			fout.flush();
		}
		if (mask & 0x0f)
		{
			std::cout << input;
		}
	}
};


Is what I am trying to do feasible? If so, is string& the right type of input? I'd like to be able to accept anything that an ostream or ofstream object can accept.
Last edited on
I made a class which does this, would you like me to post it, or you want to go through the challenges yourself?

Anyway : friend keyword : allows the function to have access to the class's private and protected members.

You should return a MyLogger& because this is what allows you to chain calls like : log << text1 << numbers << blahblah;, instead of just a simple call.
An example would be very helpful R0mai. I've updated my function as per your recommendations:

So the MyLogger& type will allow multiple uses of the << operator to be used inline. This is very useful to know!
Last edited on
I think I'm very close. string& was not correct. I believe that it needs to accept const char*. The following simplified code is close, but still doesn't compile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <fstream>
using namespace std;

ofstream fout;
class MyLogger
{
public:
	MyLogger()	{	fout.open("test.txt");	}
	~MyLogger()	{	fout.close();	}

	friend MyLogger& operator<<(MyLogger& output, const char* input)
	{
		fout << input;
		fout.flush();
		cout << input;
	}
};

int main()
{
	MyLogger TheLog();
	TheLog << "Testing";
	return 0;
}


This yields the following compiler errors:
error C2296: '<<' : illegal, left operand has type 'MyLogger (__cdecl *)(void)'
error C2297: '<<' : illegal, right operand has type 'const char [8]'
Last edited on
In this situation remove the keyword "friend." It isn't defined correctly in this situation.
If I remove "friend" I get more errors:
error C2804: binary 'operator <<' has too many parameters
error C2333: 'MyLogger::operator <<' : error in function declaration; skipping function body


How would you recommend using the friend keyword?
Last edited on
Here is what I made with comments :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>

class logger {
public:

    logger( std::ostream& os_, unsigned mask_ = 0 );


    //this allows you to do things like :
    //logger log(std::cout);
    //log(123);
    //and it returns a logger class so you can chain << to this as well. (read below for explanation)
    logger& operator()(unsigned mask_);



    void setmask(unsigned mask_);

    //functions declared with the friend keyword are not member functions,
    //but can see private members of the enclosing class
    template<class T>
    friend logger& operator<<( logger& log, const T& output );

private:

    std::ostream& os;
    unsigned mask;

};

logger::logger( std::ostream& os_, unsigned mask_ ) : os(os_), mask(mask_) {}

logger& logger::operator()(unsigned mask_) {
    setmask(mask_);
    return *this;
}

void logger::setmask(unsigned mask_) {
    mask = mask_;
}

//return type is logger& because you want to be able to chain <<() calls : (log << text1 << numbers << blahblah;)
//so :
//(log) has the type logger, so we can call operator<<()
//(log << text1) also has the type logger, we still can call operator<<() ( in fact it is the same logger instance (log) )
//and so on..
template<class T>
logger& operator<<( logger& log, const T& output ) {
    //see? normal function, but has access to log.mask and log.os because of friend declaration above
    log.os << "Mask(" << log.mask << ") : " << output << std::endl; 
    return log;
}

int main() {
    logger log(std::cout);

    log << "Hi";
    log(123) << "Hello";

    return 0;
}


I will review your code when I get home.
This is much more applicable than I had even hoped for. Thank you R0mai. I admit that I don't understand all of what is going on here, but I am dedicated to learning what you did. The comments make it much simpler.

I now understand that the << overloader was never supposed to be a member of a class, but rather its own function. Making it a friend simply ensures that it has access to members of that class such as the mask.

The operator() function lets us set something in the log class and then use it with the << operator in the same line.

I have to admit that I am not entirely sure how your constructor works, but that may be a good place to define my ofstream object and I am going to have to read up on the template keyword.

This would have taken me much more time than expected. I hope you don't mind if I use your code here as a template and then modify to suit my needs. It is perfect! Thank you so much.
Last edited on
I'm happy I could help :)

For figuring out the constructor you should read up on "Initialization List".

Templates are very interesting, you will enjoy learning them. As you get more and more comfortable with them, you will see how many possible ways are there to use them. Look up template metaprogramming, for it's extreme uses.
I spent ages writing a log class like this with loads of additional features. Having spent hours designing, coding and bugtesting, I realised there was logging functionality in the Boost library already which was doubtless way better than what I had done...
Topic archived. No new replies allowed.