Output of User-Defined Types

Output of User-Defined Types

Imagine you have written a class Person:

//
//  Person.h
//  iostreams - User-defined types
//
//  Created by Bryan Higgs on 9/4/24.
//

#ifndef Person_h
#define Person_h

#include <iostream>

class Person
{
public:
  // Constructor
  Person(const std::string &nm,
         const std::string &street = "", 
         const std::string &town = "",
         const std::string &state = "",
         int a = -1);
  
  // Access member functions
  const std::string &getName() const { return m_name; }
  const std::string &getStreet() const { return m_street; }
  const std::string &getTown() const { return m_town; }
  const std::string &getState() const { return m_state; }
  const int getAge() const { return m_age; }
  
  // Mutator member functions
  void setStreet(const std::string &street) { m_street = street; }
  void setTown(const std::string &town) { m_town = town; }
  void setState(const std::string &state) { m_state = state; }
  void setAge(int age) { m_age = age; }

private:
  std::string m_name;
  std::string m_street;
  std::string m_town;
  std::string m_state;
  int  m_age;
};

#endif /* Person_h */

You’d like to be able to say something like:

Person p("Fred Bloggs");
// ...
cout << p;

How can you accomplish this?

What’s required for the implementation of an overloaded operator << for class Person?

Look at the above expression:

  • operator << is a binary operator
  • its left-hand operand is a type ostream
  • its right-hand operand is a type Person
  • you’d like to be able to string this expression together with others:
    cout << "Person #" << id << " is: " << p
    (which associates left to right.)

Questions:

  • Can the resulting overloaded operator << function be a member function of the class Person?
  • What should its argument list contain?
    How many arguments?  What types?  All const?
  • What should its return type be?

Answers:

  • It cannot be a member function of class Person, because the left-hand operand is not of type Person.
  • Since it can’t be a member function of class Person, then it needs to be a regular function, but it still (typically) needs access to the internals of class Person — it should therefore be a friend of class Person.
  • Its argument list should reflect the left and right operands of the expression:
    (ostream &os, const Person &p)
  • Since the associativity of the expression:
    cout << "Person #" << id << " is: " << p;
    is left-to-right, it is equivalent to:
    (((cout << "Person #") << id) << " is: ") << p;
    which means that an expression like:
    cout << foo
    must return a type reference to ostream (ostream &).

Example

So, we end up with a function that looks like:

ostream &operator<<(ostream &os, const Person &p);

So, in the class, it looks like:

//
//  Person.h
//  iostreams - User-defined types
//
//  Created by Bryan Higgs on 9/4/24.
//

#ifndef Person_h
#define Person_h

#include <iostream>

class Person
{
public:
  // Constructor
  Person(const std::string &nm,
         const std::string &street = "", 
         const std::string &town = "",
         const std::string &state = "",
         int a = -1);
  
  // Access member functions
  const std::string &getName() const { return m_name; }
  const std::string &getStreet() const { return m_street; }
  const std::string &getTown() const { return m_town; }
  const std::string &getState() const { return m_state; }
  const int getAge() const { return m_age; }
  
  // Mutator member functions
  void setStreet(const std::string &street) { m_street = street; }
  void setTown(const std::string &town) { m_town = town; }
  void setState(const std::string &state) { m_state = state; }
  void setAge(int age) { m_age = age; }
  
  friend
  std::ostream &operator<<(std::ostream &os, const Person &p);

private:
  std::string m_name;
  std::string m_street;
  std::string m_town;
  std::string m_state;
  int  m_age;
};

#endif /* Person_h */

Here’s the Person.cpp file:

//
//  Person.cpp
//  iostreams - User-defined types
//
//  Created by Bryan Higgs on 9/4/24.
//

#include "Person.h"
#include <iostream>

// Constructor
Person::Person(const std::string &name,
       const std::string &street,
       const std::string &town,
       const std::string &state,
       int age)
  : m_name(name), m_street(street), m_town(town),
    m_state(state), m_age(age)
{ }

// Overloaded operator <<
std::ostream &operator<<(std::ostream &os, const Person &p)
{
  return os << "Name:   " << p.m_name << std::endl
            << "Street: " << p.m_street << std::endl
            << "Town:   " << p.m_town << std::endl
            << "State:  " << p.m_state << std::endl
            << "Age:    " << p.m_age << std::endl;
}

… and here’s a program to test this:

//
//  main.cpp
//  iostreams - User-defined types
//
//  Created by Bryan Higgs on 9/4/24.
//

#include <iostream>
#include "Person.h"

int main(int argc, const char * argv[]) 
{
  Person p("Fred Bloggs",
           "15 Highland Terrace",
           "Cleveland",
           "OH",
           34);
  
  std::cout << p;

  return 0;
}

which outputs the following:

Name:   Fred Bloggs
Street: 15 Highland Terrace
Town:   Cleveland
State:  OH
Age:    34
Program ended with exit code: 0

Index