From Basic to Intermediate: Operator Overloading (IV)
Introduction
The previous article “From Basic to Intermediate: Operator Overloading (III)” explained and demonstrated how to implement the overloading of logical and comparison operators. Despite all the simplicity I try to bring to my articles so that everyone can understand how to create their own solutions, operator overloading is one of the topics that causes the most confusion among beginner programmers. So, dear reader, don't assume that you already know how to do these things just because you read the previous article. It is very important that you practice and experiment in different situations so that you can properly understand this topic.
Well, if logical operators and comparison operators can already cause quite a bit of confusion and problems, what we will discuss here will be even more complicated. This is because we are going to look at another operator that can be overloaded. I am referring to the index operator, and since it is rarely used on its own, we will also discuss the assignment operator. So, it is time to set aside anything that might distract you and focus on what we will be covering in this article. As some people like to say: this is where the fun really begins.
Operator Overloading (IV)
Given the capabilities of MQL5, what we will cover here will bring the topic of operator overloading to a close. At least, it will conclude the simplest part of it, since there are other possibilities that make this topic much more advanced and can be very confusing for beginners. We will address this as we write and publish new articles. However, in my other profile, I will discuss a rather interesting way to use this overloading. We will save that for another time.
So, the most important feature of the [] operator—also known as the index operator—is that it is not typically implemented in isolation. When we overload this operator, we usually need to overload the assignment operator as well. This is because we will be assigning a value by index or reading a value by index. In any case, we should not rule out the possibility—or even the necessity—of implementing two operators at once.
Since this index operator can be quite confusing, I will try to show how its overloading is implemented so that everyone can understand exactly what is being done. So, let's start with something really simple. This is shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stList 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. int value; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stList *operator[](const uchar arg) 14. { 15. Print("Value ", arg, " was passed as being an index to the operator []"); 16. 17. return GetPointer(this); 18. } 19. //+----------------+ 20. }; 21. //+------------------------------------------------------------------+ 22. void OnStart(void) 23. { 24. stList demo; 25. 26. demo[4] = 10; 27. } 28. //+------------------------------------------------------------------+
Code 01
If you try to compile this Code 01, which does absolutely nothing, you will see that the compiler will produce several errors, as shown in the following figure.

Figure 01
Figure 01 illustrates some issues that it is very important for you, my dear reader, to understand thoroughly. The first is related to the error shown in the first line of Figure 01. This error occurs because, in line 17 of Code 01, we are effectively trying to return access to an object of the class declared on line 4. However, this reference will be directly linked to the variable declared on line 9. It is not recommended to return a reference to a private class member. This approach violates the principle of encapsulation, which makes the code extremely dangerous and makes it difficult to fix if errors occur. We need to return a pointer that we can then use.
So, in previous articles, we have already seen how to fix this type of error, shown in Figure 01. However, we will not do it that way here. We will take a different approach—one that is a little more challenging at first, but as you practice what we will cover here, it will seem easier and easier to you.
All right, the last two errors in Figure 01 are directly related to the assignment operator. This is because the compiler cannot determine how to handle the assignment in this situation. Keep in mind that we will be taking a different approach here, so some things may not be entirely clear at first. So, let's modify Code 01 as shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stList 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. int value; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stList *operator[](const uchar arg) 14. { 15. Print("Value ", arg, " was passed as being an index to the operator []"); 16. 17. return GetPointer(this); 18. } 19. //+----------------+ 20. void operator=(const int arg) 21. { 22. Print("Value ", arg," is being assigned to the internal variable of the class."); 23. } 24. //+----------------+ 25. }; 26. //+------------------------------------------------------------------+ 27. void OnStart(void) 28. { 29. stList demo; 30. 31. demo[4] = 10; 32. } 33. //+------------------------------------------------------------------+
Code 02
Now the compiler will be able to generate an executable file. When we run it in the MetaTrader 5 terminal, we will get the result shown below.

Figure 02
Well, at first glance, it does not seem like anything significant. But that is not the case. Let me explain why, so that you can understand what happened here. If you look at line 31, you will see something quite familiar to anyone who already knows how to work with arrays. However, despite this apparent similarity, what we are doing here is something entirely different from simply working with arrays.
For this reason, it is important to understand what is happening here in Code 02 before moving on to the next step. Please note that when Code 02 is executed, the code on line 13 is executed first, followed by the code on line 20. Understanding this is important for making sense of the actual overloading being implemented here. Depending on what you are trying to do, this sequence may be different. So pay attention to these small details.
All right, let's move on to the next point. At first, we will move very slowly so that you can understand every little detail of what is being done here. The next step is to understand what line 31 of Code 02 actually does. This can be seen in the snippet below.
. . . 26. //+------------------------------------------------------------------+ 27. void OnStart(void) 28. { 29. stList demo; 30. 31. demo.operator[](4).operator=(10); 32. } 33. //+------------------------------------------------------------------+
Snippet 01
This snippet shows how the compiler will interpret the code included in the OnStart procedure in Code 02. It is very important to understand this and grasp the concept clearly in order to understand why the code we will discuss next actually works.
Good, the next step is shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stList 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. int value; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stList *operator[](const uchar arg) 14. { 15. Print("Value ", arg, " was passed as being an index to the operator []"); 16. 17. return GetPointer(this); 18. } 19. //+----------------+ 20. stList *operator=(const int arg) 21. { 22. value = arg; 23. 24. return GetPointer(this); 25. } 26. //+----------------+ 27. void Debug(const int arg) 28. { 29. Print("Debugging line ", arg); 30. Print(value); 31. } 32. //+----------------+ 33. }; 34. //+------------------------------------------------------------------+ 35. void OnStart(void) 36. { 37. stList demo; 38. 39. (demo[4] = 10).Debug(__LINE__); 40. } 41. //+------------------------------------------------------------------+
Code 03
The execution result is shown in the following figure.

Figure 03
Great, we have code that works correctly using this simple approach. Before moving on to something more complex, I would like to explain a few details that may prove crucial to you in the future, dear reader. One of them is specifically related to the assignment operator. In order for you to truly understand what I am trying to explain, we need to give the index operator some practical functionality—even if it is very simple. For this reason, we will modify the code so that it becomes useful in practice. This results in the following code.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. union un_0 10. { 11. ulong u64; 12. uchar u8[8]; 13. }m_info; 14. //+----------------+ 15. public : 16. //+----------------+ 17. void operator=(const ulong arg) 18. { 19. m_info.u64 = arg; 20. } 21. //+----------------+ 22. void Debug(const int arg) 23. { 24. PrintFormat("Info is 0x%I64X", m_info.u64); 25. } 26. //+----------------+ 27. }; 28. //+------------------------------------------------------------------+ 29. void OnStart(void) 30. { 31. stDemo demo; 32. 33. demo = 0x1020304050607080; 34. 35. demo.Debug(__LINE__); 36. } 37. //+------------------------------------------------------------------+
Code 04
When executed, Code 04 will produce the result shown below.

Figure 04
Please note that in this case, in Code 04, assignment operator overloading is implemented in such a way that only a single assignment is possible. We also cannot directly call the debug procedure on the same line on which we perform the assignment. As you can see, this can be done exactly as shown. In practice, however, we usually do things differently, dear reader. In practice, we usually return a reference so that we can perform chained assignments. We will come back to that shortly.
But first, I want you to take a look at one more thing. Please note that on line nine, we define a union, and inside it we have a static array. Now, pay attention to what we are about to do, because it will help us explain some interesting details about using the index operator together with the assignment operator. So, we will modify the code once more, and it will look as shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. union un_0 10. { 11. ulong u64; 12. uchar u8[8]; 13. }m_info; 14. uchar m_index; 15. //+----------------+ 16. public : 17. //+----------------+ 18. stDemo *operator[](const uchar arg) 19. { 20. m_index = arg; 21. return GetPointer(this); 22. } 23. //+----------------+ 24. void operator=(const ulong arg) 25. { 26. m_info.u64 = arg; 27. } 28. //+----------------+ 29. void Debug(const int arg) 30. { 31. PrintFormat("Info is 0x%I64X", m_info.u64); 32. } 33. //+----------------+ 34. }; 35. //+------------------------------------------------------------------+ 36. void OnStart(void) 37. { 38. stDemo demo; 39. 40. demo = 0x1020304050607080; 41. demo.Debug(__LINE__); 42. demo[1] = 0xFA; 43. demo.Debug(__LINE__); 44. } 45. //+------------------------------------------------------------------+
Code 05
When we run Code 05, we'll get the result shown below.

Figure 05
Clearly, what we see in Figure 05 is not the result we expected. So why did we get this result? The reason is precisely that we are once again not overloading the assignment operator. How is that possible? This might seem a little confusing. In fact, it is much easier than you might think, dear reader. Understand this: when the compiler tries to use the assignment operator, it will only see what we define on line 24. Whereas, it will interpret the construct as shown on line 40, rather than as we intend on line 42. To change this, we need the code to look as shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. union un_0 10. { 11. ulong u64; 12. uchar u8[8]; 13. }m_info; 14. uchar m_index; 15. //+----------------+ 16. public : 17. //+----------------+ 18. stDemo *operator[](const uchar arg) 19. { 20. m_index = arg; 21. return GetPointer(this); 22. } 23. //+----------------+ 24. void operator=(const uchar arg) 25. { 26. m_info.u8[m_index] = arg; 27. } 28. //+----------------+ 29. void operator=(const ulong arg) 30. { 31. m_info.u64 = arg; 32. } 33. //+----------------+ 34. void Debug(const int arg) 35. { 36. PrintFormat("Info is 0x%I64X", m_info.u64); 37. } 38. //+----------------+ 39. }; 40. //+------------------------------------------------------------------+ 41. void OnStart(void) 42. { 43. stDemo demo; 44. 45. demo = 0x1020304050607080; 46. demo.Debug(__LINE__); 47. demo[1] = 0xFA; 48. demo.Debug(__LINE__); 49. } 50. //+------------------------------------------------------------------+
Code 06
All right, take note: we are now creating another overloaded version of the same operator.
To learn more about how overloading works and how to implement it, check out the article “From Basic to Intermediate: Overloading”; it explains in detail all the basic principles and concepts related to implementing overloading. Well, it looks like the code is correct and, in principle, should produce the result we need. However, when you try to compile Code 06, you will get the following compiler message.

Figure 06
But what is this strange behavior? I do not understand why the compiler is reporting this type of error, since, as far as I can tell, the code is implemented completely correctly. Indeed, my dear reader, as far as we're concerned, the code is implemented correctly. The problem is that the compiler interprets it differently. Figure 06 shows the reason. Please note that the compiler cannot determine which function or procedure to call in order to correctly compile line 47. This is precisely because there are several overloaded versions of the assignment operator.
I understand that at first this situation may seem a bit strange, precisely because of what was explained in the article where we discussed function and procedure overloading. However, it is very easy to correct the error shown in Figure 06. We just need to tell the compiler which overloaded version to use. To do this, we will modify line 47, as shown in the following code snippet.
. . . 40. //+------------------------------------------------------------------+ 41. void OnStart(void) 42. { 43. stDemo demo; 44. 45. demo = 0x1020304050607080; 46. demo.Debug(__LINE__); 47. demo[1] = (uchar) 0xFA; 48. demo.Debug(__LINE__); 49. } 50. //+------------------------------------------------------------------+
Snippet 02
Now the compiler will be able to generate an executable file. Therefore, when we run this executable file, we will get the result shown in the following figure.

Figure 07
All right, now we have a starting point for understanding other aspects related to these two operators. But perhaps you think the index is incorrect. If you have come to that conclusion, all you need to do is modify the code as shown in the following code snippet.
. . . 23. //+----------------+ 24. void operator=(const uchar arg) 25. { 26. m_info.u8[sizeof(m_info) - m_index - 1] = arg; 27. } 28. //+----------------+ . . .
Snippet 03
Now, when we run the code again, the result will be as shown below.

Figure 08
That is very interesting, isn't it, dear reader? However, what we have covered here is only the basic and least interesting part of applying operator overloading to these operators. So, perhaps we should look at something more interesting to move a little further along?
So, in the article “From Basic to Intermediate: Queues, Lists, and Trees (IV)” we showed how to implement a doubly linked list. With that code, we can do something that was not yet possible at the time. This is because, at that time, we did not yet have the mechanisms that we have discussed here in this article. And the mechanism I am talking about is precisely the overloading of the indexing and assignment operators. However, there is one small detail that requires us to do something a little differently. But let's take it step by step so that everything is clear and does not get more complicated than it needs to be.
So, let's take one of the code examples discussed in the article mentioned above. This will help us understand how to use overloading in a situation like this. The code we will be using is shown below.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. stList <T> *loc, 024. *ptr1 = start, 025. *ptr2 = NULL; 026. 027. for (uint c = 0; (ptr1 != NULL) && (c < index); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 028. 029. loc = new stList <T>; 030. (*loc).info = arg; 031. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 032. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 033. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 034. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 035. 036. counter++; 037. } 038. //+----------------+ 039. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 040. { 041. if ((prev == NULL) || (start == NULL)) 042. return false; 043. 044. stList <T> *loc = (index < counter ? start : prev), 045. *ptr = NULL; 046. 047. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 048. if (loc == NULL) return false; 049. 050. if (index == 0) 051. { 052. start = (*loc).start; 053. if (start != NULL) (*start).prev = NULL; 054. } else if (index >= (counter - 1)) 055. { 056. prev = (*loc).prev; 057. if (prev != NULL) (*prev).start = NULL; 058. } 059. else 060. { 061. (*ptr).start = (*loc).start; 062. (*loc).start.prev = ptr; 063. } 064. arg = (*loc).info; 065. delete loc; 066. counter--; 067. 068. return true; 069. } 070. //+----------------+ 071. bool Exclude(const uint index) 072. { 073. T tmp; 074. 075. return Restore(tmp, index); 076. } 077. //+----------------+ 078. void Debug(void) 079. { 080. Print("===== DEBUG ====="); 081. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 082. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 083. Print("================="); 084. } 085. //+----------------+ 086. }; 087. //+------------------------------------------------------------------+ 088. void OnStart(void) 089. { 090. stList <char> list; 091. 092. list.Store(10); 093. list.Store(84); 094. list.Store(-6); 095. list.Store(47, 0); 096. 097. list.Debug(); 098. 099. list.Exclude(3); 100. list.Store(35, 2); 101. 102. list.Debug(); 103. 104. for (char info; list.Restore(info, 0);) 105. Print(info); 106. }; 107. //+------------------------------------------------------------------+
Code 07
When we run Code 07, we will get the result shown in the following figure.

Figure 09
Now pay close attention, my dear reader, because this point is very important. Code 07 creates a list, as we saw in the article mentioned earlier. However, using operator overloading directly in this case has consequences unless you properly modify the implementation of the linked list. This may seem somewhat contradictory, since Code 07 works correctly. However, as it stands now, if you try to implement operator overloading in just any way, you will end up with a serious problem—or, at the very least, with something that will not be very useful in practice.
To make all the necessary changes, we need to explain one more detail that we have not discussed yet. Nevertheless, we can implement a more basic version of the overloading discussed in this article so that Code 07 will be at least a little easier to understand when we come back to it later. In principle, the changes we would have had to make would have made lines 95 and 100 somewhat clearer. However, there is a price to pay for that.
Therefore, the result we see—at least at first—will differ slightly from the one shown in Figure 09. However, as we move forward and I explain one more detail that can also be used here, the final result will be exactly as shown in Figure 09. However, the code for the OnStart procedure will be completely different from the one shown in Code 07. And, of course, a significant portion of the stList class code will also change. This is due to the much more intensive use of operator overloading.
So, let's start making the changes. First, we will remove the Exclude function, which is located on line 71 of Code 07. But why are we removing this function right now? Because this removal will only be temporary. And, as strange as it may seem, the Exclude function makes it difficult to access and determine the index within the index operator, which makes the results much harder to understand. Later, we will add the Exclude function again, although it will be used a little differently.
All right. Below you can see the new code. This is transitional code between code 07 and the code we will arrive at later.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. stList <T> *loc, 024. *ptr1 = start, 025. *ptr2 = NULL; 026. 027. for (uint c = 0; (ptr1 != NULL) && (c < index); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 028. 029. loc = new stList <T>; 030. (*loc).info = arg; 031. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 032. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 033. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 034. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 035. 036. counter++; 037. } 038. //+----------------+ 039. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 040. { 041. if ((prev == NULL) || (start == NULL)) 042. return false; 043. 044. stList <T> *loc = (index < counter ? start : prev), 045. *ptr = NULL; 046. 047. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 048. if (loc == NULL) return false; 049. 050. if (index == 0) 051. { 052. start = (*loc).start; 053. if (start != NULL) (*start).prev = NULL; 054. } else if (index >= (counter - 1)) 055. { 056. prev = (*loc).prev; 057. if (prev != NULL) (*prev).start = NULL; 058. } 059. else 060. { 061. (*ptr).start = (*loc).start; 062. (*loc).start.prev = ptr; 063. } 064. arg = (*loc).info; 065. delete loc; 066. counter--; 067. 068. return true; 069. } 070. //+----------------+ 071. // bool Exclude(const uint index) 072. // { 073. // T tmp; 074. 075. // return Restore(tmp, index); 076. // } 077. //+----------------+ 078. stList <T> *operator[](const uint arg) 079. { 080. stList <T> *loc = start; 081. 082. return loc; 083. } 084. //+----------------+ 085. void operator=(const T arg) 086. { 087. info = arg; 088. } 089. //+----------------+ 090. void Debug(void) 091. { 092. Print("===== DEBUG ====="); 093. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 094. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 095. Print("================="); 096. } 097. //+----------------+ 098. }; 099. //+------------------------------------------------------------------+ 100. void OnStart(void) 101. { 102. stList <char> list; 103. 104. list.Store(10); 105. list.Store(84); 106. list.Store(-6); 107. list.Store(47, 0); 108. 109. list.Debug(); 110. 111. // list.Exclude(3); 112. list.Store(35, 2); 113. 114. list.Debug(); 115. 116. for (char info; list.Restore(info, 0);) 117. Print(info); 118. }; 119. //+------------------------------------------------------------------+
Code 08
Now, when we run code 08, we get the result shown below.

Figure 10
However, take note of code 08, because on line 78 we begin implementing the overloading of the index operator. In addition, on line 85, we have already implemented the first overload of the assignment operator. Despite this, the code for the OnStart procedure remains virtually the same. The purpose of Code 08 is precisely to produce the result shown in Figure 10. Therefore, we can make the following change, which will make the OnStart procedure look slightly different, as you can see below.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. stList <T> *loc, 024. *ptr1 = start, 025. *ptr2 = NULL; 026. 027. for (uint c = 0; (ptr1 != NULL) && (c < index); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 028. 029. loc = new stList <T>; 030. (*loc).info = arg; 031. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 032. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 033. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 034. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 035. 036. counter++; 037. } 038. //+----------------+ 039. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 040. { 041. if ((prev == NULL) || (start == NULL)) 042. return false; 043. 044. stList <T> *loc = (index < counter ? start : prev), 045. *ptr = NULL; 046. 047. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 048. if (loc == NULL) return false; 049. 050. if (index == 0) 051. { 052. start = (*loc).start; 053. if (start != NULL) (*start).prev = NULL; 054. } else if (index >= (counter - 1)) 055. { 056. prev = (*loc).prev; 057. if (prev != NULL) (*prev).start = NULL; 058. } 059. else 060. { 061. (*ptr).start = (*loc).start; 062. (*loc).start.prev = ptr; 063. } 064. arg = (*loc).info; 065. delete loc; 066. counter--; 067. 068. return true; 069. } 070. //+----------------+ 071. // bool Exclude(const uint index) 072. // { 073. // T tmp; 074. 075. // return Restore(tmp, index); 076. // } 077. //+----------------+ 078. stList <T> *operator[](const uint arg) 079. { 080. stList <T> *loc = start; 081. for (uint c = 0; (loc != NULL) && (c < arg); loc = (*loc).start, c++); 082. return loc; 083. } 084. //+----------------+ 085. void operator=(const T arg) 086. { 087. info = arg; 088. } 089. //+----------------+ 090. void Debug(void) 091. { 092. Print("===== DEBUG ====="); 093. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 094. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 095. Print("================="); 096. } 097. //+----------------+ 098. }; 099. //+------------------------------------------------------------------+ 100. void OnStart(void) 101. { 102. stList <char> list; 103. 104. list.Store(10); 105. list.Store(84); 106. list.Store(-6); 107. list.Debug(); 108. list[0] = 47; 109. list.Debug(); 110. list[2] = 35; 111. 112. list.Debug(); 113. 114. for (char info; list.Restore(info, 0);) 115. Print(info); 116. }; 117. //+------------------------------------------------------------------+
Code 09
Now, when you run this code 09 in the MetaTrader 5 terminal, the result will be as shown below.

Figure 11
Well, that is certainly strange. But I see that the result is different from the one shown in the previous figures. Is there really no way to preserve the previous results? Well, I do not know why you, my dear reader, would want to do something like that—after all, it will just make everything much harder to understand in the end. However, before I show you how to do this, I want you to note that the index operator itself now allows you to access the list as if it were an array. Something that used to be impossible.
But let's assume—and I want to make this crystal clear, since this will not be included in the attached materials—that you want to preserve the same behavior, or result, as in Code 08, but while using the OnStart procedure from Code 09. How could we do that? So, to achieve this, we need to make a slight change to the code, as shown below.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. this[index > counter ? counter : index] = arg; 024. } 025. //+----------------+ 026. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 027. { 028. if ((prev == NULL) || (start == NULL)) 029. return false; 030. 031. stList <T> *loc = (index < counter ? start : prev), 032. *ptr = NULL; 033. 034. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 035. if (loc == NULL) return false; 036. 037. if (index == 0) 038. { 039. start = (*loc).start; 040. if (start != NULL) (*start).prev = NULL; 041. } else if (index >= (counter - 1)) 042. { 043. prev = (*loc).prev; 044. if (prev != NULL) (*prev).start = NULL; 045. } 046. else 047. { 048. (*ptr).start = (*loc).start; 049. (*loc).start.prev = ptr; 050. } 051. arg = (*loc).info; 052. delete loc; 053. counter--; 054. 055. return true; 056. } 057. //+----------------+ 058. // bool Exclude(const uint index) 059. // { 060. // T tmp; 061. 062. // return Restore(tmp, index); 063. // } 064. //+----------------+ 065. stList <T> *operator[](const uint arg) 066. { 067. stList <T> *loc, 068. *ptr1 = start, 069. *ptr2 = NULL; 070. 071. for (uint c = 0; (ptr1 != NULL) && (c < arg); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 072. 073. loc = new stList <T>; 074. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 075. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 076. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 077. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 078. 079. counter++; 080. 081. return loc; 082. } 083. //+----------------+ 084. void operator=(const T arg) 085. { 086. info = arg; 087. } 088. //+----------------+ 089. void Debug(void) 090. { 091. Print("===== DEBUG ====="); 092. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 093. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 094. Print("================="); 095. } 096. //+----------------+ 097. }; 098. //+------------------------------------------------------------------+ 099. void OnStart(void) 100. { 101. stList <char> list; 102. 103. list.Store(10); 104. list.Store(84); 105. list.Store(-6); 106. list.Debug(); 107. list[0] = 47; 108. list.Debug(); 109. list[2] = 35; 110. 111. list.Debug(); 112. 113. for (char info; list.Restore(info, 0);) 114. Print(info); 115. }; 116. //+------------------------------------------------------------------+
Code 10
When using Code 10, keep in mind that IT WILL NOT BE INCLUDED IN THE ATTACHED MATERIALS for obvious reasons; you will be able to get the result shown below in Figure 12.

Figure 12
Please note that a significant portion of Figure 12 is very similar to what we see in Figure 10. This shows that Code 10 can produce the same result as Code 08, but using operator overloading. Now take a look at the following in Code 10. If you look inside the class, you will see that the code that used to be in the Store procedure is now in the implementation of the overloaded index operator. In addition, the Store procedure actually redirects the call to the overloaded operator. When you see this, you might think: We no longer need the Store procedure. We can use the code from the following snippet directly, and the list will still be created with the same result as shown in Figure 12.
. . . 098. //+------------------------------------------------------------------+ 099. void OnStart(void) 100. { 101. stList <char> list; 102. 103. list[0] = 10; 104. list[1] = 84; 105. list[2] = -6; 106. list.Debug(); 107. list[0] = 47; 108. list.Debug(); 109. list[2] = 35; 110. 111. list.Debug(); 112. 113. for (char info; list.Restore(info, 0);) 114. Print(info); 115. }; 116. //+------------------------------------------------------------------+
Snippet 04
Yes, dear reader, you can use the code from snippet 04. However, I would like to remind you that the whole point of operator overloading is precisely to make the code simpler and easier to understand. But that is NOT what is happening here. Looking at snippet 04 and considering that in many cases the class will indeed be located in a header file, you might mistakenly conclude that the linked list should contain only three values, rather than five—as you can verify by running the code.
And what, in my opinion—and in the view of many other programmers as well—is even more confusing is the following. If you look only at this snippet 04, you might think that the list starts with the value 10 and ends with the value -6, and that when lines 107 and 109 are executed, these values will be replaced with 47 and 35, respectively. It is precisely these kinds of issues that you must always be careful about when implementing operator overloading.
And that is exactly why this Code 10 WILL NOT BE AVAILABLE IN THE ATTACHED MATERIALS. It is included here solely as an interesting detail for this article.
One last detail before wrapping up this article
Very well, everything seems perfect. Everything looks very nice and straightforward. However, there is a minor limitation that complicates matters slightly when it comes to overloading the index operator in MQL5. At least as of the time of writing this article, I have not found a way to get around this limitation.
And this limitation, if I may put it that way, is somewhat frustrating. The fact is that in MQL5, we CANNOT use pointers in the same way as in languages such as C or even C++. This complicates some key issues when it comes to operator overloading. I know that many people may not understand what I am about to explain, but those who have been programming for a while will understand it perfectly.
When we overload the index operator, that is, the [] operator, we DO NOT RETURN values the way we have done here in this article. Usually, we return a reference to the memory area where the value we want to change is stored. Except in more complex cases, where something very similar to what we have discussed here is actually used.
Naturally, when code 03 was shown, many people were probably already quite interested. The same thing probably happened when code 06 was shown. But, without a doubt, enthusiasm peaked with code 09—or perhaps with code 10. However, I do not know if you have noticed, but in every case we use overloading for writing and never for reading. Why? The reason lies precisely in that MQL5 limitation I mentioned earlier: we cannot use pointers the way we do in C or C++.
To illustrate this, I will show what a very simple piece of C++ code using overloading might look like. You can see it below.
01. //+------------------------------------------------------------------+ 02. #include <stdio.h> 03. //+------------------------------------------------------------------+ 04. template <typename T> class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. T *u8; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stDemo(const int arg) 14. { 15. u8 = new T[arg]; 16. } 17. //+----------------+ 18. T &operator[](const int arg) 19. { 20. return u8[arg]; 21. } 22. //+----------------+ 23. }; 24. //+------------------------------------------------------------------+ 25. int main(void) 26. { 27. stDemo <char> demo(8); 28. char v1; 29. 30. demo[3] = 10; 31. demo[1] = demo[3]; 32. v1 = demo[1]; 33. 34. printf("%d", v1); 35. 36. return 0; 37. } 38. //+------------------------------------------------------------------+
Code 11
Here, in code 11, we have fully functional overloading. However, this code is NOT MQL5. This is C++. And I would like to draw your attention specifically to line 31 and, above all, to line 32. Please note that on line 32, we read a value and store it in a variable that does not belong to the class. However, we cannot do that in MQL5. At least, not that way.
In MQL5, you need to decide whether you want to write or read individual values using the index operator. However, as we have seen in this article, it is better to use it for writing data, since reading would involve far more limitations. This limitation on reading individual values can be worked around using a function or procedure designed exclusively for this task, if the index operator overloading is geared toward writing data.
"Wow, you really know how to ruin the party. I was so happy, glad, and content, and then you came along and ruined all my joy. But it is no big deal. Since I do not have much experience either, I cannot complain when you are willing to explain some of the details. To be honest, I will forgive you only if you show me how to do something in MQL5 that is similar to what this Code 11, written in C++, does."
So, below is the MQL5 code that produces the same result as the C++ code shown in Code 11.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. T u8[]; 10. int m_index; 11. //+----------------+ 12. public : 13. //+----------------+ 14. stDemo(const int arg) 15. { 16. m_index = -1; 17. ArrayResize(u8, arg); 18. } 19. //+----------------+ 20. stDemo <T> *operator[](const int arg) 21. { 22. m_index = arg; 23. return &this; 24. } 25. //+----------------+ 26. void operator=(const T arg) 27. { 28. u8[m_index - 1] = arg; 29. } 30. //+----------------+ 31. const T GetInfo(void) const 32. { 33. return u8[m_index - 1]; 34. } 35. //+----------------+ 36. }; 37. //+------------------------------------------------------------------+ 38. void OnStart(void) 39. { 40. stDemo <char> demo(8); 41. char v1; 42. 43. demo[3] = 10; 44. demo[1] = demo[3].GetInfo(); 45. v1 = demo[1].GetInfo(); 46. 47. Print(v1); 48. } 49. //+------------------------------------------------------------------+
Code 12
Now, pay attention. In this Code 12, written in MQL5, we have operator overloading specifically geared toward writing data to the internal structure of the object. For this reason, we can read and write data without much difficulty—provided, of course, that you use the function specified on line 31. However, please note: although Code 12 produces the same result as Code 11, written in C++, it is completely different from the following one.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> class stDemo 05. { 06. //+----------------+ 07. private : 08. //+----------------+ 09. T u8[]; 10. //+----------------+ 11. public : 12. //+----------------+ 13. stDemo(const int arg) 14. { 15. ArrayResize(u8, arg); 16. } 17. //+----------------+ 18. stDemo <T> *Set(const int arg, const T info) 19. { 20. u8[arg - 1] = info; 21. return &this; 22. } 23. //+----------------+ 24. T operator[](const int arg) 25. { 26. return u8[arg - 1]; 27. } 28. //+----------------+ 29. }; 30. //+------------------------------------------------------------------+ 31. void OnStart(void) 32. { 33. stDemo <char> demo(8); 34. char v1; 35. 36. demo.Set(3, 10); 37. demo.Set(1, demo[3]); 38. v1 = demo[1]; 39. 40. Print(v1); 41. } 42. //+------------------------------------------------------------------+
Code 13
In the case of Code 13, which is also written in MQL5, we have index operator overloading specifically geared toward reading data from the internal structure of the object. Please note that this simple change completely alters the way you will work and how you will need to implement the code. The result of all three code snippets will be exactly the same: in the terminal—whether it is a console, command line, or even MetaTrader 5—we will see the value 10 displayed. This is true in all cases.
However, you might be looking at Code 12 or even Code 13 and wondering: is there any way to combine both approaches? Or, even better: is it possible to create something similar to what we see in Code 11, written in C++? Well, dear reader, unfortunately, the answer to that question is NO. At least, I have not found a way to implement such overloading directly in MQL5. There is an alternative approach, but it involves combining code written in C++ with code written in MQL5. However, in my opinion, this is an advanced topic, and we will not be covering it in this series of articles. We may explore this topic in a future series devoted exclusively to advanced topics.
Concluding Thoughts
In this article, we took the first step toward showing how operator overloading can be implemented for the index operator, as well as for the assignment operator, while trying to offer a practical and interesting approach for everyone. However, what we have discussed here is only part of what I still want to show, and it is directly related to overloading these operators. The famous Code 07 appeared in this article, but we did not discuss how to achieve the same result using operator overloading. In the next article, we will focus exclusively on this issue. This way, you can see that if you approach the problem correctly, you can create fully functional code using operator overloading without much difficulty. As a result, the code will be much more readable. At least, that is what I think, because in my opinion the code we will discuss in the next article is much easier to understand than Code 07 from this article.
In any case, try to study and practice what we have covered here. And if you are interested in continuing to learn, do not forget: the next article will be out soon. See you next time.
| MQ5 file | Description |
|---|---|
| Code 01 | Basic Demo |
| Code 02 | Basic Demo |
| Code 03 | Basic Demo |
| Code 04 | Basic Demo |
| Code 05 | Basic Demo |
| Code 06 | Basic Demo |
| Code.cpp | Basic Demo |
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/16951
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Features of Custom Indicators Creation
Market Replay: Unity Is Strength (II)
Features of Experts Advisors
Neural Networks in Trading: The Adaptive Graph Diffusion Model (SAGDFN)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use