The usual overloaded "<<" conundrum...

Ok Gang,

Mr. Dummy (myself!! LOL) is back with another problem that you would all THINK is easy (I hope it is) but I'm screwing something up!

I am going through the "Principles and Practices Using C++" book as you probably know and I am in chapter 9 exercises (specifically exercise 9-3 (9-2 was successfully solved)..... and the #3 exercise says it needs me to to "provide a Global << operator" for printing my class and since overloading operators was introduced in this chapter I'm thinking - hey..... just overload the "<<" and voila!

Well, I have tried to overload the operator and it is not working to say the least! I am getting errors saying that A.) I need to use template syntax (HAVEN'T GOTTEN TO TEMPLATES YET!!) it also says B.) that ostream does NOT name a type and finally C.) "no match for operator<<.."

Things you should know==>

1.) I have DEFINED and DECLARED the operator overload OUTSIDE of my class. It looks like this==>

class Name_pairs{ //the class I define

. . . .

}; //end of class definition

Here's where the operator overload declaration is.

Followed closely by the definition here.

Please see below code for the EXACT look of these...

2.) I have #included <iostream> AND <ostream> for this code along with the usual suspects



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//declaration is here==>
std::ostream& std::ostream::operator<<(std::ostream& os, const Name_pairs& np);


//here's the definition==>

ostream& std::ostream::operator<<(std::ostream& os, const Name_pairs& np){
    
        for(unsigned i=0; i<np.names.size(); ++i){
            os<<np.names[i]<<" "<<np.ages[i]<<"\n";
        }   //end for
        
        os<<"\n";
        
        return os;
        
    }//end of << operator overload definition


Name_pairs is MY class that I defined for the exercise as directed by the author. I THINK I am also verbatim defining the operator overload based on the author's examples as well on page 337.

So what is going wrong here? Why am I getting errors when I THINK I giving the overload definition the right info based on the book's models??

Xanadu
Your signature is wrong. It should be:

 
std::ostream& operator<<(std::ostream& os, const Name_pairs& np);
Well hot dang Dutch!

That definitely took care of those problems! (I suppose I did NOT get that definition and declaration preamble correct.......)

NOW, it says that inside the definition that "names" and "ages" are private.... so is this telling me I need a PUBLIC member accessor function that returns these members when used by a non-member function declared OUTSIDE the class correct??

Am I getting this now?

Xanadu
You have the option of public "get" functions or making the operator<< function a "friend" of your class by putting this in the class:

 
friend std::ostream& operator<<(std::ostream& os, const Name_pairs& np);

If you make it a "friend" then it can access private members directly.
Last edited on
Dutch,

What is the "preferred" method?==> get() accessors or just making it a friend?

Also, if my members are vectors (one is a vector of strings and the other of doubles) will I have to make the get() functions return vector<string>& to access the whole vector? Or would I try to do it member by member? How would that look?

Based on what you recommend is how I will proceed.

Xanadu
Do what leaves the smallest footprint. Adding a new public function increases the public interface of your class to everyone. Making just a particular function act as friend of the class just affects that particular function.

If there is no good reason why a particular member variable should be public other than for use in the operator<< function, then just make that function a friend. But in the future, if you find yourself having to make a bunch of functions be friends of the class, it might be simpler to just making a public accessor for that member.
Did as you both said and it works!

One minor problem..... it says it's getting a "segmentation" error. What is that all about and what should I be looking at to see if I am violating usage or container sizes or whatever.

Xanadu
Fixed it. It helps if you only perform the loop in the following manner ==>

1
2
3
4
5
6
7
8
9

for(unsigned i=0; i<var.size(); ++i).......


//as opposed to....

for(unsigned i=0; i<=var.size(); ++i).......



That last one cause segmentation faults!

Xanadu
Range based for loops, also known as for-each loops, help eliminate many of the more common sources of problems when dealing with containers.

https://www.learncpp.com/cpp-tutorial/6-12a-for-each-loops
I'll consider this matter solved.

Well Furry guy, that range-for loop doesn't seem to work real well when using vectors however unless I'm missing something.......

Xanadu
You are really missing something, the ranged based for loop works very well with vectors.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <iostream>
#include <vector>
#include <numeric>

template<typename T>
void Print1DArray(std::vector<T> const&);

template<typename T>
void Print2DArray(std::vector<std::vector<T>> const&);

template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v);

template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<std::vector<T>>& v);

int main()
{
   // creating a sized vector
   std::vector<int> a1DVector(5);

   std::cout << "Displaying a sized 1D vector:\n";
   std::cout << a1DVector.size() << '\n';

   for (size_t itr = 0; itr < a1DVector.size(); itr++)
   {
      std::cout << a1DVector[itr] << ' ';
   }
   std::cout << "\n\n";

   // creating a sized vector filled with a value other than zero
   std::vector<int> another1DVec(8, 100);

   std::cout << "Displaying a filled 1D vector:\n";
   std::cout << another1DVec.size() << '\n';

   for (auto& itr : another1DVec)
   {
      std::cout << itr << ' ';
   }
   std::cout << "\n\n";

   std::cout << "Let's display that 1D vector again:\n";
   std::cout << another1DVec.size() << '\n';
   Print1DArray(another1DVec);
   std::cout << "\n\n";

   std::cout << "Let's display that 1D vector yet again:\n";
   std::cout << another1DVec.size() << '\n' << another1DVec << "\n\n";

   std::cout << "Creating a 2-dimensional vector, enter row size: ";
   int row_size;
   std::cin >> row_size;

   std::cout << "Enter column size: ";
   int col_size;
   std::cin >> col_size;

   std::cout << "\n";

   // create a 2 dimensional int vector with known dimensions
   std::vector<std::vector<int>> a2DVector(row_size, std::vector<int>(col_size));

   // initialize the vector with some values other than zero
   int start = 101;
   int offset = 100;

   // step through each row and fill the row vector with some values
   for (auto& itr : a2DVector)
   {
      std::iota(itr.begin(), itr.end(), start);
      start += offset;
   }

   // let's display the filled 2D vector
   std::cout << "Displaying the filled 2D vector:\n";
   Print2DArray(a2DVector);
   std::cout << '\n';

   std::cout << "Let's display that 2D vector again:\n";
   std::cout << a2DVector;
}


template<typename T>
void Print1DArray(std::vector<T> const& a1DVec)
{
   for (size_t itr = 0; itr < a1DVec.size(); itr++)
   {
      std::cout << a1DVec[itr] << ' ';
   }
}


template<typename T>
void Print2DArray(std::vector<std::vector<T>> const& a2DVec)
{
   for (const auto& row_itr : a2DVec)
   {
      for (const auto& col_itr : row_itr)
      {
         std::cout << col_itr << ' ';
      }
      std::cout << '\n';
   }
}


template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& v)
{
   for (auto const& x : v)
   {
      std::cout << x << ' ';
   }

   return os;
}


template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<std::vector<T>>& v)
{
   for (auto const& x : v)
   {
      std::cout << x << '\n';
   }

   return os;
}
Displaying a sized 1D vector:
5
0 0 0 0 0

Displaying a filled 1D vector:
8
100 100 100 100 100 100 100 100

Let's display that 1D vector again:
8
100 100 100 100 100 100 100 100

Let's display that 1D vector yet again:
8
100 100 100 100 100 100 100 100

Creating a 2-dimensional vector, enter row size: 4
Enter column size: 5

Displaying the filled 2D vector:
101 102 103 104 105
201 202 203 204 205
301 302 303 304 305
401 402 403 404 405

Let's display that 2D vector again:
101 102 103 104 105
201 202 203 204 205
301 302 303 304 305
401 402 403 404 405
Yes, but it works not-as-well if the container access looks like how it looks in the first post.
1
2
3
        for(unsigned i=0; i<np.names.size(); ++i){
            os<<np.names[i]<<" "<<np.ages[i]<<"\n";
        }   //end for 


The design [flaw?] here is that np is an object that contains multiple independent arrays, and it's up to the user to make sure that names.size() == ages.size(), which at first glance seems to be a bad design from the start.
If it was rather something like this:
1
2
3
4
class Foo {
    string name;
    int age;
};

And we had a vector<Foo>, then a for-each loop would work just as well.
Last edited on
std::vector works with custom classes. It would have helped if you had included your class layout, I had to make some guesses to get the code to work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <string>
#include <vector>

class Name_pairs
{
public:
   Name_pairs(std::string name, unsigned age) : my_name(name), my_age(age) { }

   friend std::ostream& operator<<(std::ostream&, const std::vector<Name_pairs>&);

private:
   std::string my_name;
   unsigned    my_age;
};


int main()
{
   std::vector<Name_pairs> np;

   np.push_back(Name_pairs("Joe", 12));
   np.push_back(Name_pairs("Fred", 23));
   np.push_back(Name_pairs("Pete", 18));

   std::cout << np << '\n';
}

std::ostream& operator<<(std::ostream& os, const std::vector<Name_pairs>& np)
{
   for (const auto& itr : np)
   {
      os << itr.my_name << ", " << itr.my_age << '\n';
   }

   return os;  // have to return so the output "chains up" properly
}
Joe, 12
Fred, 23
Pete, 18
You have the option of public "get" functions or making the operator<< function a "friend" of your class by putting this in the class:

There's another option that I use sometimes, mostly because I'm so lazy that I don't want to type the operator<< declaration more than once, or even copy/paste it.

The other option is a public function to prints the data, then the << operator calls that function:

1
2
3
4
5
6
7
class MyClass {
public:
   std::ostream &print(ostream &os) const;  // print the object to os
   ...
};

std::ostream & operator << (ostream &os, const MyClass &c)  { return c.print(os); }


I don't think there's a compelling reason for or against this method. It's just another option.
Ganado is correct!

The way the problem was worded, I was forced to use vectors to INTERNALLY represent the data in my class rather than just making a vector of my class' objects which I agree with you all would be a MUCH smarter and easier way to implement than having them on the inside of the object. This seemed very weird to me but I figured ...... maybe there is something here he is trying to show (like why you DON'T do this??) through the exercise??

So yes, having vectors representations of the data INSIDE the object as members made everything way worse and way more stupid! Having them as a single member like Furry Guy pointed out seemed like the obvious way from the start.

Xanadu
Topic archived. No new replies allowed.