Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overloading operator<<

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

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

Which would make the arrays look something like this:

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

I can handle the logic of checking the input to make sure everything matches up, but I'm confused with the technical details of operator<<.

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

Here is my code so far:

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, the user of my class can do something like this, which is unwanted:

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

What can I do to enforce the alternating nature of the input?

like image 279
Maxpm Avatar asked Jan 21 '23 08:01

Maxpm


2 Answers

The reason ostream operators return a reference to the ostream, and the reason it somewhat helped your case to return a reference to MyClass is that an expression like A << B << C is always interpreted like (A << B) << C. That is, whatever the first overloaded operator returns becomes the left-hand side of the next operator call.

Now, if you want an expression like MyInstance << "First text" << 1 << 2 to produce 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 might do what you want (thanks to @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;
}

Those would be the only two overloaded operator<< functions you define related to MyClass. This way, every time you call an operator<<, the compiler switches the return type from MyClass& to MyClass_ExpectInt or vice-versa, and passing the "wrong" sort of data to operator<< is never allowed.

like image 54
aschepler Avatar answered Jan 28 '23 12:01

aschepler


MyInstance << "First text" << 1;

This line calls operator<<(operator<<(MyInstance, "First text"), 1). If operator<< didn't return a reference to MyClass, the "outer" call would fail, as you'd be passing void where a MyClass & is expected.

In order to enforce the alternating types compile-time, you need to make a helper class, e.g. MyClassHelper. You then need to create these two operators:

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

Each operator should then return a reference to the "other" involved object. This ensures that after << "string" the returned reference is a MyClassHelper, which only has an operator<< for int. And after << int, the returned reference is a MyClass which only has an operator<< for string

like image 33
Erik Avatar answered Jan 28 '23 12:01

Erik