Operator overloading

Pages: 123
The code is actually a sample from a book


Which one?
IIRC the Date class and overloading operators can be found in "Sams Teach Yourself C++ in One Hour a Day". I own several editions of the book, up to the eighth edition.

Chapter 12 is the key in the editions I own.

Sams books aren't IMO the best learning sources. They aren't bad like other learn C++ books, but they could be better with more features shown. The Sams books treat later C++ features as an afterthought instead of integrating them in the examples from the start.

From admittedly limited experience I'd recommend a couple of other books that I'd opine are better at teaching C++, especially C++20/23. These books are really dense in what they cover with C++ and the C++20/23 features are introduced from Chapter 1.

I'd go with the C++20 books since most of those features are available in the available compilers, especially Visual Studio. VS is 100% C++20 compliant, all the other major compilers still have missing features.

The #1 choice:
"Beginning C++20 - From Novice to Professional" the book has a GitHub repo so you can download the source code.

Personally I'd get the eBook, the softcover is really flimsy.

#2 choice:
"Professional C++, Fifth Edition" Another C++20 book.

#3 choice:
"Beginning C++23 - From Beginner to Pro" this book also has a GitHub repo.
Yes, the Sam's book gets into:
static_cast (compile time)
dynamic_cast (run time)
reinterpret_cast (FORCE cast)
const_cast (work around on methods not set with const)

I have another 2 books & I started to read both partly, very early though.


5) I have another minor question from the different way you guys showed me to update operator++ (with "++*this;"). But maybe it is more of a gripe. And this is a great idea when you have tons of code there, which you can simply reuse from one operator to another.

1
2
3
4
5
6
7
8
9
	Date operator ++(int) //Postfix ++Date
	{
		Date newDate(*this);
		++*this;                             //works
		//this->operator++();          //works
                //++day;                            //works
                PassPointer thisPP(this);      //EXTRA TEST pass (&obj) to a pointer, which makes sense
		return newDate;
	}


"this" is = &object (address of the object), so can we say it is a pointer since it acts like one?
*this is = to the object itself by dereferencing.
How can I be sure when to use "this" vs "*this"


Here the pointer operator -> does the dereferencing:
 
this->operator++();   //works! (Has (&obj) and -> dereferences to use obj itself (and not address)) 



This line below works because the operator expects you to send the object itself, similar to "Date date1(date2);". The operator ++ cannot work directly with the address of the object (&obj).
 
++*this;                             //works 



If I make a function below & pass "PassPointer (this); from the class, then it will send the (&obj) which is exactly what the function needs, which is an address for the pointer.
[code]
void PassPointer(Date* copy){}
[code]

Sometimes I wish that you can just say "this" and have it be smart enough to extrapolate either the object or address of the object...I mean I gave you where the object is out figure it out compiler!!!
I am pretty sure this is for speed, rather than have it constantly checking behind the scenes, did they mean object or address.

When comparing to an int pointer it is comparable to "this":
int* pointer = (num1 = 22);
cout << pointer << endl; //(&obj) just like with "this"
cout << *pointer << endl; //22, the pointed to value.....(*this)(what it's pointing to, the obj itself!)


this is an in-built pointer to the class in which it is used. Where you need the object as opposed to pointer-to-object then you use *this.

What's the version of the Sam book and which are the other 2 books you use?
Last edited on
The C++ 11 version because I did not know about the different versions at that time and it was cheaper. I also bought the 2 books that George recommended from before, but only read up to the array chapter a few months ago and then had to step away from C++ because of life. The professional book I skimmed through and realize that I am not ready for it just yet.

I appreciate the Sam book and it has been a BIG help. I flipped through the book when I first bought it and looked at some of the code that looks like a foreign language and hieroglyphics and now I am able to read and understand it and I am appreciative of that.

I admit that I do have holes in my understanding and I want to understand things on a deeper level. When coming back now a few months later, I realized that I was memorizing the code and did not know completely what I was doing with every single aspect. Such as when to use a pointer, ref, or by val return, when to dereference, and when to use the scope resolution operator :: as opposed to the "." dot operator. Those have been my biggest problems, but after this review I think I will get better because now I am experimenting more.

Isn't that how you guys learned, first mimic then after a while you get comfortable that you can duplicate many code snippets then you start deviating from the original code to explore and learn deeper? At least in the beginning.
Have a look at
https://www.learncpp.com/

Isn't that how you guys learned, first mimic


No. Read the text, examine example(s) and then try to code examples without looking at the given examples. If can't then re-read the text etc. Only continue when can code examples and provided exercises (if any). Good intro books provide a selection. Learning to code and a language is mainly a 'do'. You have to code to learn to code. IMO you can't learn just be reading or by cut-and-paste existing code.

Professional C++ isn't a beginners book. It's more an intermediate one for when you're got some C++ knowledge/experience - but for it's audience I agree It's a great book.

I agree with George re the Beginning C++ books. IMO these are the best.

If the Sams book is just for C++11 then you're missing out on a whole load of new features that have been introduced since then. You should learn C++20.
If the Sams book is just for C++11 then you're missing out on a whole load of new features that have been introduced since then. You should learn C++20.

The 9th edition supposedly covers C++20 and C++23. Well, C++20 is talked about in 3 very short chapters at the end of the book, blink and you miss it style. The C++23 coverage is even thinner.

The C++11 edition is marginally better than the later ones.

As much as C++11 changes the language in fundamental ways to almost be entirely different programming language C++20 is itself a major change that really shouldn't be "left until later."

Buy and retain books. Lots of books. Print and eBooks. First to learn from, then as resources to be consulted later as refreshers.

https://isocpp.org/wiki/faq/how-to-learn-cpp#buy-several-books

Even older books have value as resource material. I still occasionally peruse my old 1996 "ANSI C++ in 21 Days" tome. Very much a reminder of how the language has changed, and the coding philosophy underneath.

Here's the opening code snippet in the ANSI C++ book:
1
2
3
4
5
6
#include <iostream.h>

void main()
{
   cout << "Hello World!\n";
}

It kinda look familiar, but it has a rather offensive "smell" of something aged and rotting if someone has even a passing knowledge of C++11 or later.

Here's a C++23 equivalent:
1
2
3
4
5
6
import std;

int main()
{
   std::println("Hello World!");
}


The Pro C++(20) book is a good supplemental book that should be one of many books even a semi-serious programmer has in their library. I am an earnest self-taught hobbyist when it comes to programming. My programming library covers C, C++, WinAPI, DirectX and HTML.

I am not an expert at any of this, I learn what I learn to solve something I have as a current project. Now I am delving into Git and GitHub. At least the resources for that are free and on-line. Even the eBook.
Isn't that how you guys learned, first mimic

Mimic, no. Read and cruft my own version of the language feature I just read about, absolutely. Several GBs of HD space is devoted to my testing snippets I've mashed up over time.

Here's an example bit of code I did several years ago, pre-C++20:
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include <iostream>
#include <vector>
#include <numeric>

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

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

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

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

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

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

   std::cout << "\n";

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

   std::cout << "Let's verify the sizes of the 2D vector....\n";
   std::cout << "The vector has " << aVector.size() << " rows.\n";
   std::cout << "The vector has " << aVector[0].size() << " columns.\n\n";

   // display the empty 2D vector
   std::cout << "Displaying the empty 2D vector:\n";
   Display2DVector(aVector);
   std::cout << '\n';

   std::cout << "Initializing the 2D vector with some values....\n";
   // initialize the vector with some values other than zero
   int start = 101;
   int offset = 100;

   for (auto& itr : aVector)  // remember the &!!!!!!!!!!!!!!!!!!!!!
   {
      std::iota(itr.begin(), itr.end(), start);
      start += offset;
   }

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

   std::cout << "Let's try that again....\nDisplaying the filled 2D vector:\n";
   std::cout << aVector;
   std::cout << '\n';

   std::cout << "Adding a value to the end of the 2nd row.\n";
   aVector[1].push_back(999);

   Display2DVector(aVector);
   std::cout << '\n';

   std::cout << "Let's try that again....\nDisplaying the filled 2D vector:\n";
   std::cout << aVector;
   std::cout << '\n';

   // create a smaller vector
   std::vector<std::vector<int>> aVec2(2, std::vector<int>(2));

   std::cout << "Let's get some values for a 2 x 2 vector:\n";
   Get2DVectorValues(aVec2);
   std::cout << '\n';


   std::cout << "Let's display the new values:\n";
   std::cout << aVec2;
}

template<typename T>
void Display2DVector(const std::vector<std::vector<T>>& aVec)
{
   for (unsigned rows = 0; rows < aVec.size(); rows++)
   {
      for (unsigned cols = 0; cols < aVec[rows].size(); cols++)
      {
         std::cout << aVec[rows][cols] << ' ';
      }
      std::cout << '\n';
   }

   // an easier method for looping through and displaying a 2D vector
   // without referencing the rows and columns
   /*
   for (const auto& row_itr : aVec)
   {
      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<std::vector<T>>& v)
{
   for (auto const& x : v)
   {
      os << x << '\n';
   }

   return os;
}


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

   return os;
}

template<typename T>
void Get2DVectorValues(std::vector<std::vector<T>>& aVec)
{
   for (size_t rows = 0; rows < aVec.size(); rows++)
   {
      for (size_t cols = 0; cols < aVec[0].size(); cols++)
      {
         std::cout << "[" << rows << "][" << cols << "]: ";
         int input;
         std::cin >> input;
         aVec[rows][cols] = input;
      }
   }
}

My main goal was to be able to output a 2D stdlib container (a vector) as easily in code as a regular variable like an it.

As I found out I can process 1D cotainers as well, and 3D is "doable" as long as the output isn't really critical.
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
#include <iostream>
#include <vector>

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

int main()
{
   std::cout << "Creating a 3-dimensional vector, enter depth size: ";
   int depth;
   std::cin >> depth;

   std::cout << "Enter row size: ";
   int row;
   std::cin >> row;

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

   std::cout << "\n";

   // create a 3 dimensional int vector with known dimensions
   using std::vector;
   vector<vector<vector<int>>> aVector(depth, vector<vector<int>>(row, vector<int>(col, 0)));

   // let's display the initial 3D vector
   std::cout << aVector << '\n';

   // initialize the vector with some values
   for (int depth_loop = 0; depth_loop < depth; depth_loop++)
   {
      for (int row_loop = 0; row_loop < row; row_loop++)
      {
         for (int col_loop = 0; col_loop < col; col_loop++)
         {
            aVector[depth_loop][row_loop][col_loop] = (((depth_loop + 1) * 100)
                                                       + ((row_loop + 1) * 10)
                                                       + col_loop + 1);
         }
      }
   }

   // let's display the filled 3D vector
   std::cout << aVector << '\n';
}


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

   return os;
}

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

   return os;
}
What I was trying to say is that I mimic the code from the book until I can write the code without looking & I have been able to do this up to the polymorphic chapters. Sometimes I try to change things up & try something on my own, then I discover what looked simpler needs more thought process and is not quite what I though. The book cannot cover all different combos of code, where you might run into problems if you don't know the in's & out's.

For instance book says it is cheaper to send a ref or pointer into functions, to avoid copying. So I send a return out of a func by ref and pointer but run into problems when using a variable belonging to the func and the compiler is not too happy if I send it a certain way. But I had to try this on my own & discover what the compiler likes & does not like and not just mimic the code.

I never thought that MakeMyClass(30); implicit conversion could ever work in C++ and I was surprised. And then here we are with a double step conversion in an operator that is a problem. There are other things that I wish were implicit conversions, but they are not.

Funny you should show me that code, since tomorrow is my template review day. George, you should be a programmer part time, why not when others who know less than you are doing it.
Write your own version of the code, even if it is "the same." Typing is a proven technique to enhance memorization.

Go through line by line and ask yourself what the code is doing. Even in code you already know.

I see code in a book/online doing something I am not exactly sure what it is. Most code from cppreference is that and more. It can take me a lot of poking around for hours and days before I understand what it happening. Maybe.

If you think function templates are mindblowing wait until you run across abbreviated function templates using auto. Using auto can save a lot of keystrokes in a lot of places, defining iterators or range based for loops to give a couple of examples.
I do that too now. When I first read and practiced the code sample, I just copied because that is all I could do as I did not know enough to start changing many things and yea it was all overwhelming collectively.

Now when I went over this again this time I noticed from the book:
1
2
3
4
5
6
7
	const char& operator[](int index) const
	{
		if (index < GetLength())
		{
			return buffer[index];
		}
	}


Which really is incomplete now that I notice it & I am not in just mimic mode. There is no return if index > GetLength().

1
2
3
4
5
6
7
8
9
10
11
12
13
	const char& operator[](const int index) const
	{
		if (index < GetLength())
		{
			return buffer[index];
		}
		else
		{
			cout << "Index[" << index << "] is out of bounds! Max index is [" 
			       << (GetLength() - 1) << "]." << endl;
			return smileyFace;	//Char = 2				
		}
	}


And am sure you guys will make an even better version.
Even the best code examples in books or online are for the most part not production ready code. They are stripped down to illustrate a programming point, leaving out the gobs and gobs of extra code that are "sanity checks" for unexpected (and expected) problems so the program can gracefully end after notifying the user of some big "OOOOPS!", or recover and continue on as needed.

Sadly most of the "from the ground up" approach to teaching C++ is really bad code that only later lessons points out briefly why it is bad code.

C++ is really dense, with a very steep learning curve. Especially as the C++ ISO Committee keeps shoe-horning in more 'stuff' very rapidly in the quixotic quest of making it "easier to code."

I've been self-learning C++ since before the first C++ standard, C++98. Only in the last couple of years have my attempts to make me a "beep, boop, beep, I Are Programmer!" have I been achieving something other than a lot of wasted time. As the C++ toolbox grows more expansive the more I get comfortable understanding how the stdlib works. For what I want to do.

Having really good tools like Visual Studio Community certainly helped as well.
The book code is incorrect as non-void functions should always return a value.

As the else follows a return, else isn't needed in this case.

1
2
3
4
5
6
7
8
9
const char& operator[](const int index) const
{
	if (index < GetLength())
		return buffer[index];

	cout << "Index[" << index << "] is out of bounds! Max index is [" 
			       << (GetLength() - 1) << "].\n";
	return smileyFace;	//Char = 2				
}


You could do:

1
2
3
4
5
6
7
8
9
10
const char& operator[](const int index) const
{
	if (const auto len {GetLength(); index < len)
		return buffer[index];
        else {
	        cout << "Index[" << index << "] is out of bounds! Max index is [" 
			       << (len - 1) << "].\n";
	        return smileyFace;	//Char = 2				
        }
}


In this case the else is required as len is local only to the scope of the if statement.

Also, how do you differentiate between a returned smileyFace and character from buffer?
Last edited on
how to you differentiate between a returned smileyFace and character from buffer?

That is a good one.

@SubZeroWins: Could you list the strategies for error reporting that you know of?
Yea, I did think about that, but in this case I am only intentionally using the alphabet and punctuations. Otherwise one could make a method to check against special characters in the array and avoid them being added.

But if the array accepted all 256 ASCII char's then my inclination would be to send NULL, but the problem is NULL will show up as 0 and confuse ASCII 0. And one can't and shouldn't dereference the null character anyways, although you can. So this can also cause differentiation issues between NULL (0) & ASCII 0;

I had thought about it too before and thought maybe you can use a template & send back the const char& or the cout stream as string, but that could be a problem too all depending on what your doing with the code on the other end and not a very good idea. Not to mention the terror some programmers will have when seeing it.

So then maybe use some extended ASCII & pick a foreign character that you will definitely not use. How would you guys do it?

My error checking is very limited, basically check against NULL, Try/Catch, & new(nowthrow).
Good excuse to practice my error checking without looking...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
using namespace std;

int main()
{
	try
	{ 
		int* pMem = new int[ 0x1fffffff ];
		delete[] pMem;
	}
	catch (bad_alloc)
	{
		cout << "Memory allocation failed!!!" << endl;
	}

	int* pMem2 = new(nothrow) int[0x1fffffff];
	if (pMem2)
		delete[] pMem2;
	else
		cout << "Memory allocation failed!!!" << endl;

	return 0;
}
Last edited on
If'n you have the "Beginning C++20" book you might want to look at chapter 13, it deals with operator overloading with a different class. Including the pre- and post increment/decrement operators. There is also overloading logical operators that includes the new Spaceship operator <=>.

Fair warning, if you don't understand modules yet, especially if your compiler doesn't support them, you are gonna be more than a bit lost. The entire book from chapter 1 uses C++20 features.

Yes, there are non-module examples, but if you are going to use C++20 you really should use modules.

I mention this because I am currently doing a refresher read of the C++20 book and am working on chapter 13. Next in my deque is reading up on C++23 with "Beginning C++23".

You might want to look at smart pointers for memory management instead of doing it "by hand" with new and delete.

You'd still have to check if the smart pointer was successful in glomming onto its chunk of memory, that "sanity check" will not go away.
The Spaceship operator was the first thing I read in that book, since I was flipping through it and came across it somehow and just had to find out what the heck that was.

I am just doing sample code now but I really should re-read my original book too as my perspective is better than what it was originally when I first read it. I reviewed my macro, templates, template specialization, variadic expressions, static_assert, & static var in a template today. I will do tuples later, as I am not crazy about them. Going to review my STL's now.

I did new/delete manual dynamic memory allocation, since I am sure I will come across it in code that I will look up. So it is better to know.


I am curios about this from below:
 
const char* pChar{ "Hello" };	

This creates a const char pointer to a (const char array???) and not a string object, correct? Or is it not even considered a char array but just a pointer in memory for a sequence of chars?

My clues are that I can't seem to use any of the string methods (.length() & .size()), so what it points to is not stored as a string object. I also lose the sizeof() for the array, because when you create a pointer one loses the size info. I can still get size with (strlen() + 1).

Is there anything else I should know about this, or is that it?


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
#include <iostream>
#include <string>
using namespace std;



int main()
{
	const char myChar[6]{ 'H', 'e', 'l', 'l', 'o', '\0' };
//const char* pMyChar = (const char myChar[6] { 'H', 'e', 'l', 'l', 'o', '\0' });//NOT WORK!

	cout << myChar << endl;						               //Hello

	
	const char* pMyChar = myChar;				//Can still do yet
	cout << "sizeof() = " << sizeof(myChar) << endl;	//6
	cout << "strlen() = " << strlen(myChar) << endl;	//5
	cout << pMyChar << endl;					//Hello
	cout << pMyChar[0] << endl;					//H
	cout << *(pMyChar + 1) << endl;				//e
	cout << *(++pMyChar) << endl;				//e
	cout << *(--pMyChar) << endl;				        //H (move pointer back)
	//cout << (unsigned int)myChar[5] << endl;	        //FORCE '\0' lookup, dangerous!
	cout << "***************" << endl;


	const char* pChar{ "Hello" };				
	cout << pChar << endl;					       //Hello!

	cout << strlen(pChar) << endl;				       //5
	cout << sizeof(pChar) << endl;				       //8, size of pointer storage
	cout << sizeof(*pChar) << endl;			      //1, size of 1st element
	

	for (int i = 0; i < strlen(pChar); ++i)
		cout << pChar[i] << endl;

	//Force '\0' lookup, dangerous I know!
	cout << (unsigned int)pChar[strlen(pChar) + 1] << endl;	//0

	return 0;
}
Last edited on
This
 
const char* pChar{ "Hello" };
means the same as
 
const char* pChar = "Hello";

pChar is technically just a pointer to the first char in the string literal.

The const refers to the thing being pointed to, i.e. you're not allowed to modify what the pointer points to but you can modify the pointer itself and make it point at some other char if you want.
Last edited on
the problem is NULL will show up as 0
0 != '0'
That is, the numeric value of character 0 is not 0 in the ASCII.

In your case the question is, can the this->buffer ever have nulls as valid characters?


Yes, function can return values of which the caller can tell whether they are invalid or not. Examples: https://cplusplus.com/reference/string/string/find/ and functions that return a pointer.

A function could return a tuple that has both a value and a bool. Example: https://cplusplus.com/reference/set/set/insert/

A function could take additional (reference or pointer) parameter, where it reports errors. Example: https://doc.qt.io/qt-6/qstring.html#toUInt

A function could write to (global) variable. C Standard Library does use https://cplusplus.com/reference/cerrno/errno/

It is also possible to throw an exception, like https://cplusplus.com/reference/vector/vector/at/ does.
For returning a value where there might also be an error there's also std::optional (C++17) and std::expected (C++23). This shows examples of returning when converting a string to a number:
https://www.cppstories.com/2024/constexpr-number-parsing-cpp23/

https://en.cppreference.com/w/cpp/utility/optional
https://en.cppreference.com/w/cpp/utility/expected

There's also the old way of returning the value by a ref-param with the function returning a bool.

In this case there is also the c way where you return an int and not a char with a negative meaning an error.

Also as the function param is an int, what happens if a negative value is passed as it meets the less than test? Shouldn't the param be unsigned? Then you only need the less than test.
Pages: 123