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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With