Overload Operator <<

I am making a simple class that uses operator<< . It will store two parallel data arrays, each with a different (but already known) data type. The idea is that the final interface will look something like this:

 MyInstance << "First text" << 1 << "Second text" << 2 << "Third text" << 3; 

To make arrays look something like this:

 StringArray: | "First text" | "Second text" | "Third text" | IntArray: | 1 | 2 | 3 | 

I can process the input validation logic to make sure everything matches, but I'm confused by the technical details of operator<< .

The tutorials I checked say to overload it as a friend function with the return type std::ostream& , but my class has nothing to do with streams. I tried using void as the return type, but got compilation errors. In the end, I ended up returning a class reference, but I'm not sure why this works.

Here is my code:

 class MyClass { public: MyClass& operator<<(std::string StringData) { std::cout << "In string operator<< with " << StringData << "." << std::endl; return *this; // Why am I returning a reference to the class...? } MyClass& operator<<(int IntData) { std::cout << "In int operator<< with " << IntData << "." << std::endl; return *this; } }; int main() { MyClass MyInstance; MyInstance << "First text" << 1 << "Second text" << 2 << "Third text" << 3; return 0; } 

Additionally, a user of my class might do something like this, which is undesirable:

 MyInstance << "First text" << 1 << 2 << "Second text" << "Third text" << 3; 

What can I do to alternate the nature of the input?

+4
source share
3 answers

The ostream operators return a link to ostream , and the reason it helped your case to return a link to MyClass somewhat is because an expression like A << B << C always interpreted as (A << B) << C That is, no matter which first overloaded operator returns, it becomes the left side of the next operator call.

Now, if you want an expression of type MyInstance << "First text" << 1 << 2 generate a compiler error, you need to make sure that the type returned after the first two operators << is a type that cannot be called with another int . I think something like this can do what you want (thanks @Pete Kirkham for a good improvement idea):

 struct MyClass_ExpectInt; class MyClass { private: friend MyClass& operator<<(const MyClass_ExpectInt&, int); void insert_data(const std::string& StringData, int IntData); // ... }; struct MyClass_ExpectInt { MyClass& obj_ref; std::string str_data; explicit MyClass_ExpectInt( MyClass& obj, const std::string& str ) : obj_ref( obj ), str_data( str ) {} }; MyClass_ExpectInt operator<<( MyClass& obj, const std::string& StringData ) { // Do nothing until we have both a string and an int... return MyClass_ExpectInt( obj, StringData ); } MyClass& operator<<( const MyClass_ExpectInt& helper, int IntData ) { helper.obj_ref.insert_data( helper.str_data, IntData ); return helper.obj_ref; } 

These will be the two overloaded operator<< functions that you define associated with MyClass . Thus, every time you call operator<< , the compiler switches the return type from MyClass& to MyClass_ExpectInt or vice versa, and passing the "wrong" data type to operator<< never allowed.

+7
source
 MyInstance << "First text" << 1; 

This line calls operator<<(operator<<(MyInstance, "First text"), 1) . If operator<< did not return the link to MyClass , the "external" call will fail, as you will skip the void where MyClass & is expected.

To enforce compilation time for variables, you need to create a helper class, for example. MyClassHelper. Then you need to create these two statements:

 MyClassHelper & operator<<(MyClass &, std::string const &); MyClass & operator<<(MyClassHelper &, int); 

Then each operator must return a link to a "different" object. This ensures that after << "string" returned link is MyClassHelper, which has only the <<operator for int. And after << int returned link is MyClass, which has only the <<operator for the string

+2
source

In addition to what schepler has to say: syntax

 x << "one" << 1 << "two" << 2; 

It does not show that โ€œoneโ€ belongs to 1. It looks good today, but tomorrow it will be very difficult to explain to someone else, and even more difficult to deploy as an engineer in two years. This only happens because it looks like a โ€œregularโ€ insert statement that supports more types.

If you have the freedom to choose what your API will look like, do something now to make it clear that you are actually inserting two related values.

This can be done, for example, by allowing only std::pair<string,int> be inserted:

 x << make_pair( "one", 1 ) << make_pair( "two", 2 ) ; 
0
source

Source: https://habr.com/ru/post/1342035/


All Articles