
From Basic to Intermediate: Template and Typename (IV)
Introduction
The materials provided here are for educational purposes only. It should not be considered in any way as a final application. Its purpose is not to explore the concepts presented.
In the previous article: “From Basic to Intermediate: Template and Typename (III)“, we started a topic that many newbies find particularly challenging. This is because many people have not understood a concept that is very important to MQL5 programmers: the concept of templates. Since I understand that many readers know very little about programming, I try to make the material as didactic as possible.
Therefore, we ended the previous article rather abruptly, it ended with an image of an error and a code that did not generate an executable file. I know that many people might be disappointed to see something like this in an article. However, I have just begun to introduce you to a topic that turns out to be quite difficult when you first get to know it: the topic of type overloading. In fact, what we are currently creating is not a type overload, but a template type that allows the compiler to generate the right type for each situation that we need to handle.
Since, in principle, any code that is provided in an article should work, we have made a small limitation in the explanation. But I am trying to explain it so that you understand that the code does not always work when we implement it. I know many people who want to learn how to solve problems in their code. However, the vast majority of them cannot solve problems simply because they do not have the right ideas about either this resource or this programming language. And without this it is difficult to explain how to solve certain types of tasks that are a trifle for professional programmers, but a big problem for a beginner.
So far, we've been considering overloading functions and procedures using a template, but when applied to other types of applications, things get a little more complicated, as shown at the end of the previous article. So let us start a new topic.
Using a template type
In fact, the concept to be applied is simple. However, it is quite difficult to visualize it in such a way as to implement the concept correctly. We shall start with what we have already seen in the previous article. But before that, we shall consider the source code that works and can be compiled. It is just below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. //+----------------+ 07. #define macro_Swap(X) for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--) \ 08. { \ 09. tmp = X.u8_bits[i]; \ 10. X.u8_bits[i] = X.u8_bits[j]; \ 11. X.u8_bits[j] = tmp; \ 12. } 13. //+----------------+ 14. union un_01 15. { 16. ulong value; 17. uchar u8_bits[sizeof(ulong)]; 18. }; 19. 20. { 21. un_01 info; 22. 23. info.value = 0xA1B2C3D4E5F6789A; 24. PrintFormat("The region is composed of %d bytes", sizeof(info)); 25. PrintFormat("Before modification: 0x%I64X", info.value); 26. macro_Swap(info); 27. PrintFormat("After modification : 0x%I64X", info.value); 28. } 29. 30. { 31. un_01 info; 32. 33. info.value = 0xCADA; 34. PrintFormat("The region is composed of %d bytes", sizeof(info)); 35. PrintFormat("Before modification: 0x%I64X", info.value); 36. macro_Swap(info); 37. PrintFormat("After modification : 0x%I64X", info.value); 38. } 39. } 40. //+------------------------------------------------------------------+
Code 01
When code 01 is compiled and executed in MetaTrader 5, the following results are obtained.
Image 01
Obviously, this result is not quite correct. This is connected with the highlighted area 01 in this image. But depending on the specific use case, this result will be correct. But that is not what we want. We want the value declared in line 33 to be two bytes wide. However, precisely because the union is eight bytes wide, we are forced to use this type of declaration, which makes the final result completely inadequate, as you can see in image 01.
But in the previous article, we already created the template type directly. Even if the code you saw there was not incorrect, but it was missing something, it is hard to explain why everything should be executed exactly the way we are going to do it. With this, I decided to conclude the article and give you an opportunity to calmly reflect and explore this issue. The goal is to try to understand why the code is not working. And here we will figure out how to act correctly and why the code should be implemented in a very specific way so that the compiler understands what should be done.
The next natural step would be to replace code 01 with a slightly different one. This is done before we achieve what was shown in the previous article. This results in the following code:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. //+----------------+ 07. #define macro_Swap(X) for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--) \ 08. { \ 09. tmp = X.u8_bits[i]; \ 10. X.u8_bits[i] = X.u8_bits[j]; \ 11. X.u8_bits[j] = tmp; \ 12. } 13. //+----------------+ 14. 15. { 16. union un_01 17. { 18. ulong value; 19. uchar u8_bits[sizeof(ulong)]; 20. }; 21. 22. un_01 info; 23. 24. info.value = 0xA1B2C3D4E5F6789A; 25. PrintFormat("The region is composed of %d bytes", sizeof(info)); 26. PrintFormat("Before modification: 0x%I64X", info.value); 27. macro_Swap(info); 28. PrintFormat("After modification : 0x%I64X", info.value); 29. } 30. 31. { 32. union un_01 33. { 34. ushort value; 35. uchar u8_bits[sizeof(ushort)]; 36. }; 37. 38. un_01 info; 39. 40. info.value = 0xCADA; 41. PrintFormat("The region is composed of %d bytes", sizeof(info)); 42. PrintFormat("Before modification: 0x%I64X", info.value); 43. macro_Swap(info); 44. PrintFormat("After modification : 0x%I64X", info.value); 45. } 46. } 47. //+------------------------------------------------------------------+
Code 02
Well, when we run code 02 the final result will be what we see in the image below.
Image 02
Please note that in this image 02 we got exactly what we wanted to achieve. That is, now we have a type value that is displayed and requires eight bytes. And another value that is also displayed and requires two bytes, as expected. However, look at how it was done. Even if it is functioning, we overload ourselves by creating code and making appropriate changes to it. This increases the chance of error as the code grows and becomes more complex, as we have to add more and more new elements to it.
As you can see, we are doing something very simple, but the code has started to get a little confusing. It is at this point that the idea of using type templates arises. The reason is that the only difference between the block of code that is between lines 15 and 29 and the block of code between lines 31 and 45 is in the type that is defined inside the union. You can see it in lines 18 and 34 of code 02.
Therefore, we started creating a template, the main purpose of which is to simplify the same code 02, since the union that needs to be executed is overloaded in it. Thus, application of the presented concept is used to create a template for a function or procedure, which, in this case, can be overloaded. As a result, we get such a code:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> 05. union un_01 06. { 07. T value; 08. uchar u8_bits[sizeof(T)]; 09. }; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. //+----------------+ 14. #define macro_Swap(X) for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--) \ 15. { \ 16. tmp = X.u8_bits[i]; \ 17. X.u8_bits[i] = X.u8_bits[j]; \ 18. X.u8_bits[j] = tmp; \ 19. } 20. //+----------------+ 21. 22. { 23. un_01 info; 24. 25. info.value = 0xA1B2C3D4E5F6789A; 26. PrintFormat("The region is composed of %d bytes", sizeof(info)); 27. PrintFormat("Before modification: 0x%I64X", info.value); 28. macro_Swap(info); 29. PrintFormat("After modification : 0x%I64X", info.value); 30. } 31. 32. { 33. un_01 info; 34. 35. info.value = 0xCADA; 36. PrintFormat("The region is composed of %d bytes", sizeof(info)); 37. PrintFormat("Before modification: 0x%I64X", info.value); 38. macro_Swap(info); 39. PrintFormat("After modification : 0x%I64X", info.value); 40. } 41. } 42. //+------------------------------------------------------------------+
Code 03
This is where confusion begins. And it all has to do with the following: code 03 is trying to create what is shown in code 02, but using something very similar to what is shown in code 01. However, when compiling code 03, the compiler outputs the following error message.
Image 03
There is definitely something wrong here, but at first glance it does not make any sense. Because, at first glance, we are declaring the template correctly. So what's wrong with code 03 that prevents it from compiling? If it does not compile, then the compiler does not understand what needs to be done.
Well, I know that many people are always trying to solve the problems that the compiler reports and points out to us. This is of paramount importance. But in this case, these messages being output by the compiler, which one can see in image 03, do not indicate to us what the problem is.
However, there is a concept that we explained in the first articles about variables and constants. This concept is very important now so that we understand how to act in such situations. As you can see, in line 07 of code 03 we declare a variable, right? I am just asking: what variable are we declaring in line 07? That is the whole point. If neither I nor any other programmer knows, then how will the compiler know about it?
Well, you can answer me: "In line 25, we specify that we want a type with a width of eight bytes, and in line 35, two bytes." Correct. But this does not tell the compiler which type of variable to use. Please note that in lines 25 and 35 WE DO NOT DECLARE THE VARIABLE. We assign a value to it. This declaration is contained in lines 23 and 33. Do you see the problem now?
But there is another, even more serious problem, which leads to all those error messages that can be seen in image 03. The fact is that although the declaration appeared in lines 23 and 33, it declares something unknown to the compiler, namely the type of variable in line 07. Note that lines 23 and 33 refer to the type declared in line 05. In other words, a union.
However, this union is a template. Therefore, the data type to be used is determined by the compiler. Since it does not know which data type to use, all these error messages come up. But now there is a concept that should be accepted. If you understand how the template is used in functions and procedures, you probably know that at a certain point a variable is declared. When a function or procedure is overloaded, the compiler already knows the data type, so it can create the appropriate procedure.
Knowing this, you might ask: how do I point out the compiler which data type to use? We usually use a data type followed by a variable name. And since we do this in lines 23 and 33 of code 03, I have no idea how to solve this problem. If you have reached this point and figured out all these concepts, it is time to see how to solve the problem in order to be able to use templates and overload different types. For this purpose, MQL5 uses a special declaration of variables that can be local or global, which already exist in the C and C++ languages. But remember: the main thing is to understand the concept, not just remember how to do it. The solution is just below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> 05. union un_01 06. { 07. T value; 08. uchar u8_bits[sizeof(T)]; 09. }; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. //+----------------+ 14. #define macro_Swap(X) for (uchar i = 0, j = sizeof(X) - 1, tmp; i < j; i++, j--) \ 15. { \ 16. tmp = X.u8_bits[i]; \ 17. X.u8_bits[i] = X.u8_bits[j]; \ 18. X.u8_bits[j] = tmp; \ 19. } 20. //+----------------+ 21. 22. { 23. un_01 <ulong> info; 24. 25. info.value = 0xA1B2C3D4E5F6789A; 26. PrintFormat("The region is composed of %d bytes", sizeof(info)); 27. PrintFormat("Before modification: 0x%I64X", info.value); 28. macro_Swap(info); 29. PrintFormat("After modification : 0x%I64X", info.value); 30. } 31. 32. { 33. un_01 <ushort> info; 34. 35. info.value = 0xCADA; 36. PrintFormat("The region is composed of %d bytes", sizeof(info)); 37. PrintFormat("Before modification: 0x%I64X", info.value); 38. macro_Swap(info); 39. PrintFormat("After modification : 0x%I64X", info.value); 40. } 41. } 42. //+------------------------------------------------------------------+
Code 04
Look at what we are doing at the moment, because this is a very subtle change. However, when trying to compile code 04, we see the following.
Image 04
That is, something seemingly insignificant that does not make much sense at the moment allows the code to compile. We can see the result of executing code 04 in the following image.
Image 05
What a beautiful and amazing thing, isn't it? But what happened here, why does code 04 work but code 03 does not, and why are we making this strange declaration in lines 23 and 33? "Now I am completely at a loss. Stunned. Because I absolutely do not understand what is going on here."
Okay, let us see what happened here and why the declaration must be made the way it is made in lines 23 and 33. So, this is our fourth article about templates and typename. In the second article we discussed how one can compel the compiler to use a particular data type so that the type matches the parameter that the function or procedure receives. In this case, type conversion occurs during value assignment. To do this, we used explicit type conversion by enclosing the target type in parentheses. This happens very often at different times, as you may have noticed in other codes. However, it is one thing to assign a value to an already declared variable, and another thing is to assign a type to a variable that has not yet been declared.
Since the purpose of templates is to create a model of a function, procedure, or data type that we can use in the future, they will be accompanied by typename declaration. And that is the question. As explained in previous articles, letter T accompanying the typename declaration is actually a label for defining something later. Therefore, when the compiler substitutes T, we will get the type definition that we need to use. And the compiler will be able to create the code correctly. So when in declarations in lines 23 and 33 we declare the type the way we do it, we actually tell the compiler which data type to use instead of the T that accompanies the typename declaration.
Since we have not discussed how to use this declaration in detail yet, it may take you a while to understand it. However, as you can see, it works. Therefore, the declaration should be designed in this way.
If you found this interesting, then you will like what comes next, because now we are going to convert the macro from code 04 to a function or procedure. The goal is for code 04 to continue working and get the result that is shown in image 05. But since this implies the application of today's material in a more advanced form, we will consider this issue separately.
Why complicate it when you can simplify it?
Many of you may think that our actions here are illogical, that this material is too advanced, and that we do not have to learn how to do it. In fact, I have to agree with this statement. If you only know how to create functions and procedures, declare variables, and use some basic statements, you can create almost anything. The more tools and resources we have at our disposal, the easier it is to implement and create them. With a single nail, you can even build a grain storage elevator. This has already been done. But it would be much easier if we could use more resources as “nails”.
Here and now we will create a function that will replace the macro as defined in line 14 of code 04. This will be very interesting. But first, I must remind all of you that our goal is to learn. Before trying to understand what we are doing here, it is necessary to understand what was done in the previous topic. Only then will everything make sense.
Farther. Let us start with the initial idea. That is, remove the macro from code 04 and try to turn it into a function. But before we do that, let us create a procedure that, in my opinion, is easier to understand. Then you will be able to create the following code, shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> 05. union un_01 06. { 07. T value; 08. uchar u8_bits[sizeof(T)]; 09. }; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. { 14. un_01 <ulong> info; 15. 16. info.value = 0xA1B2C3D4E5F6789A; 17. PrintFormat("The region is composed of %d bytes", sizeof(info)); 18. PrintFormat("Before modification: 0x%I64X", info.value); 19. Swap(info); 20. PrintFormat("After modification : 0x%I64X", info.value); 21. } 22. 23. { 24. un_01 <ushort> info; 25. 26. info.value = 0xCADA; 27. PrintFormat("The region is composed of %d bytes", sizeof(info)); 28. PrintFormat("Before modification: 0x%I64X", info.value); 29. Swap(info); 30. PrintFormat("After modification : 0x%I64X", info.value); 31. } 32. } 33. //+------------------------------------------------------------------+ 34. void Swap(un_01 &arg) 35. { 36. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 37. { 38. tmp = arg.u8_bits[i]; 39. arg.u8_bits[i] = arg.u8_bits[j]; 40. arg.u8_bits[j] = tmp; 41. } 42. } 43. //+------------------------------------------------------------------+
Code 05
When we try to compile code 05, to our surprise and disappointment, the following result appears on the screen. It is provided just below.
Image 06
Again? It's not funny anymore. Calm down, there is no reason to panic or despair. So far. The problem is very similar to the one that was mentioned in the previous topic. But in this case, the solution is different. Please note the following: in lines 19 and 29, the procedure is called in line 34. So far, so good. The problem is that the procedure expects exactly the type of data that is defined in line 05. And since this type is a template, the compiler does not know what to do with it, because we cannot tell it which data type to use.
"But wait. What do you mean? We declare the data type in lines 14 and 24." Yes, but in these lines 14 and 24 we locally define the data type to be used. However, this is not passed to the procedure from line 34, precisely because it is a complex type, and not a primary one, as it was in the past when everything was passed as it is. There is one more little thing. Since the data type allows type overloading, when trying to pass it inside a function or procedure, we must make sure that the function or procedure CAN ALSO BE OVERLOADED. That is why I said that such moments are interesting.
That is, since the argument can be overloaded in OnStart procedure, the function or procedure that received the same type of data must also be overloaded. Thus, everything will be suitable and the compiler will be able to understand the code to create the desired executable file.
Once we understand this and know how to create an overloaded function which uses a template, take code 05 and modify it to create code 06, which we see just below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> 05. union un_01 06. { 07. T value; 08. uchar u8_bits[sizeof(T)]; 09. }; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. { 14. un_01 <ulong> info; 15. 16. info.value = 0xA1B2C3D4E5F6789A; 17. PrintFormat("The region is composed of %d bytes", sizeof(info)); 18. PrintFormat("Before modification: 0x%I64X", info.value); 19. Swap(info); 20. PrintFormat("After modification : 0x%I64X", info.value); 21. } 22. 23. { 24. un_01 <ushort> info; 25. 26. info.value = 0xCADA; 27. PrintFormat("The region is composed of %d bytes", sizeof(info)); 28. PrintFormat("Before modification: 0x%I64X", info.value); 29. Swap(info); 30. PrintFormat("After modification : 0x%I64X", info.value); 31. } 32. } 33. //+------------------------------------------------------------------+ 34. template <typename T> 35. void Swap(un_01 &arg) 36. { 37. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 38. { 39. tmp = arg.u8_bits[i]; 40. arg.u8_bits[i] = arg.u8_bits[j]; 41. arg.u8_bits[j] = tmp; 42. } 43. } 44. //+------------------------------------------------------------------+
Code 06
Great! Now we have a correct code. The compiler will finally be able to figure out what we are trying to do. And so, we will ask the compiler to create an executable file, since overloading using a template has been implemented, as you can see in code 06. However, unfortunately, the compiler tells us what we see below.
Image 07
"Now this system is definitely bullying us. Nothing else. I am sorry, but I don't understand why this happened." Do not worry, dear readers. We are doing something that forces many people to create codes in the same way every time, turning things that could have been done much easier and with less chance of error into something monstrous and repeating various points that could have been improved.
Actually, creating a template is not the easiest task. Therefore, it is difficult to see codes that use this type of resource. However, understanding this will help us in many points, as it greatly simplifies the code from a programming point of view, since it transfers all the complexities to the compiler. Now let us figure out why this error occurs. At first, such things make us suffer a lot. I have been trying to learn how to do this correctly for a long time, since similar elements are often used in C and C++. Since MQL5 is based on many concepts found in C and C++, once I learned how to work with these languages, learning MQL5 turned out to be as easy as ABC. Almost like a child's game. But it was quite difficult to learn what we are explaining here.
Be careful. Although the compiler reports that the error is in lines 19 and 29, it is leading us to the wrong place. It is not compiler's fault, it is ours. Let me explain why. Do you remember that in the previous topic it was necessary to take steps so that the compiler could understand what data we are using? We have created a declaration that can be seen in code 06, in lines 14 and 24.
If this happens correctly in OnStart procedure, then this does not happen in Swap procedure. One might even think that the template declaration of Swap procedure is correct, but this is not entirely true. This was discussed in three previous articles. But it is hard to see the error here. If you do not see where the error is, this is because you have not yet figured out how the function or procedure template handles overloading.
Please note that when declaring the template in line 34, we indicate that we are going to use T type to create an overload, right? Now look at line 35 again. Which argument, which in this case is the only one, gets this T type? There is no argument that accepts this T type. That is, although the declaration says that we are creating a template, it is not actually used. Now let us return to previous articles and see how the declaration was made. You will see something similar to this:
. . . 15. template <typename T> 16. T Averange(const T &arg[]) . . .
Snippet 01
In snippet 01, note the following fact: T type is declared in line 15, but immediately after that we use it in line 16. This is necessary so that the argument can use the type when the compiler creates a function or procedure. If this is not done, the compiler will not know how to proceed. The same thing happens in code 06: we have a declaration, but we do not use it. In other words, we do not tell the compiler how to use the declaration. To do this, we should change code 06 again so that the compiler understands what we want to do. Below is the code that will work.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. template <typename T> 05. union un_01 06. { 07. T value; 08. uchar u8_bits[sizeof(T)]; 09. }; 10. //+------------------------------------------------------------------+ 11. void OnStart(void) 12. { 13. { 14. un_01 <ulong> info; 15. 16. info.value = 0xA1B2C3D4E5F6789A; 17. PrintFormat("The region is composed of %d bytes", sizeof(info)); 18. PrintFormat("Before modification: 0x%I64X", info.value); 19. Swap(info); 20. PrintFormat("After modification : 0x%I64X", info.value); 21. } 22. 23. { 24. un_01 <ushort> info; 25. 26. info.value = 0xCADA; 27. PrintFormat("The region is composed of %d bytes", sizeof(info)); 28. PrintFormat("Before modification: 0x%I64X", info.value); 29. Swap(info); 30. PrintFormat("After modification : 0x%I64X", info.value); 31. } 32. } 33. //+------------------------------------------------------------------+ 34. template <typename T> 35. void Swap(un_01 <T> &arg) 36. { 37. for (uchar i = 0, j = sizeof(arg) - 1, tmp; i < j; i++, j--) 38. { 39. tmp = arg.u8_bits[i]; 40. arg.u8_bits[i] = arg.u8_bits[j]; 41. arg.u8_bits[j] = tmp; 42. } 43. } 44. //+------------------------------------------------------------------+
Code 07
Of course, now we have a working code that provides the same result as in Figure 05. But I am asking you: have you managed to figure out why code 07 works? Imagine that in a programming exam you are asked to create a code that generates the same result as code 07 using a function or procedure.
But without using a template, and necessarily using the union template declared in line 04 of code 07. Would you be able to do that? Would you be able to solve this problem in order to get a job as a programmer, or would you just say that it is impossible? At some point, we mentioned that a procedure or function, in this case, must use a template to be generated.
There are concepts and details that make things quite interesting. For example, we can create code 07 in another way to directly manage overloading without using a template for this. Since understanding such things requires an appropriate explanation, we will consider this issue in the next article.
Final considerations
In this article, we discussed how to manage templates and create them in a more general and precise way. Since I know that this content is very difficult to understand right away, as I went through it myself when I studied C and C++ at the beginning of my programming career, I ask you to be patient. I recommend that you practice what has been shown in these articles and, above all, try to understand the concepts presented. If you understand them, you can create almost any code, and with much less difficulty than those who insist on memorizing code snippets and syntax of a programming language. Therefore, practice and study the content of the application calmly. And we will see you in the next article.
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/15670
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.





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use