Implementing a custom stream inserter and extractor for user-defined types

One of the powerful features of C++ is the ability to overload the stream insertion (<<) and extraction (>>) operators for custom user-defined types. This allows us to seamlessly stream objects of our own classes into and out of streams, such as std::cout and std::cin. In this blog post, we will walk through the process of implementing a custom stream inserter and extractor for a user-defined type.

The Custom Type

Let’s begin by defining a simple class called Person that represents a person’s basic information, including their name and age.

class Person {
private:
    std::string name;
    int age;
    
public:
    Person(const std::string& name, int age)
        : name(name), age(age) {}
        
    // ...
};

Stream Insertion Operator

To enable streaming a Person object to an output stream, we need to overload the << operator. This can be done as a friend function of the Person class:

std::ostream& operator<<(std::ostream& os, const Person& person) {
    os << "Name: " << person.name << ", Age: " << person.age;
    return os;
}

Here, we simply output the respective member data of the Person object to the output stream.

Stream Extraction Operator

To enable reading a Person object from an input stream, we need to overload the >> operator. Again, this can be done as a friend function of the Person class:

std::istream& operator>>(std::istream& is, Person& person) {
    std::cout << "Enter name: ";
    std::getline(is, person.name);

    std::cout << "Enter age: ";
    is >> person.age;

    // Ignore remaining characters in the input stream
    is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    return is;
}

In this implementation, we prompt the user to enter the name and age of the person. We use std::getline to read the name, as it allows us to handle inputs with spaces. Then we use is >> person.age to read the age. Finally, we ignore any remaining characters in the input stream to ensure it’s in a clean state for the next input operation.

Putting it all Together

Now, let’s see our custom stream inserter and extractor in action:

int main() {
    Person p1("John Doe", 28);

    std::cout << "Person p1: " << p1 << std::endl;

    Person p2;
    std::cout << "Enter person details: ";
    std::cin >> p2;

    std::cout << "Person p2: " << p2 << std::endl;
    
    return 0;
}

In this example, we create a Person object p1 with a name and age and print it using the overloaded << operator. Then, we create another Person object p2 and take user input for its details using the overloaded >> operator, and finally display the entered details using the << operator again.

Conclusion

Implementing custom stream inserter and extractor operators allows us to seamlessly integrate our user-defined types with the C++ streams. It simplifies the process of input and output operations, making our code more concise and readable. By following the steps outlined in this blog post, you can easily implement custom stream insertion and extraction for your own user-defined types. #CPP #CustomStreamOperators