Maxpm
Maxpm

Reputation: 25642

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?

Upvotes: 4

Views: 302

Answers (3)

xtofl
xtofl

Reputation: 41519

In addition to what schepler has to say: the syntax

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

Does not show that "one" belongs with 1. It looks nice to you today, but is going to be very hard to explain to someone else tomorrow, and even harder to reverse-engineer by the maintainer in two years. That is just because it resembles the 'regular' insertion operator, which does support more types.

If you have the freedom to choose what your API will look like, better do something now to make it clearer that you're really inserting two associated values.

This can be done by e.g. allowing only std::pair<string,int> to be inserted:

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

Upvotes: 0

aschepler
aschepler

Reputation: 72473

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.

Upvotes: 7

Erik
Erik

Reputation: 91320

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

Upvotes: 2

Related Questions