
From Basic to Intermediate: Template and Typename (II)
Introduction
In the previous article, "From Basic to Intermediate: Template and Typename (I)", we started talking about a rather complicated but very fascinating topic: creating templates for functions and procedures. Since this topic is quite difficult to consider and explain in several articles, we are going to divide it into more articles. However, we will not delve too deeply into this topic alone before proceeding to other ones that are no less interesting, because there are things that will only make sense if other topics are touched upon, as well.
But we will only work on this topic for a while, until we create a sufficiently solid and broad base so that we can move on to others, before returning to templates. After all, this topic is very extensive. In any case, it is necessary to clarify a few points that we did not mention in the previous article, precisely in order not to complicate the issue. I want you to feel comfortable studying each of these articles, and so that they help you start programming more correctly and safely, having at least a good understanding of each tool available in MQL5.
Although much of what is described here is applicable to other languages, due attention must be paid to the application of these concepts.
After these words, it is time to relax, remove possible distractions and focus on what will be discussed in this article. Here we will talk a little more about templates.
The materials provided here are for didactical purposes only. In no case should this application be considered as final, which purposes will be other than studying the presented concepts.
Templates, more templates
One of the most interesting things about templates is that, with proper planning, they become an indispensable tool. This happens because we end up creating what many might call a rapid implementation model. In other words, we no longer need to program everything, but only a part of the necessary elements.
Most likely, you have no idea what it is about. But as you study and practice programming, you will find that many things can be done much faster if you undertake certain steps. Knowing and understanding all the tools at our disposal allows us to choose the best path, DO NOT GET ATTACHED TO NAMES. Try to understand the accepted concept, and over time you will be able to take the board into your own hands and build your own path.
Let us now start with a very simple implementation based on what was shown in the previous article. It is shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(Sum(10, 25)); 07. Print(Sum(-10.0, 25.0)); 08. } 09. //+------------------------------------------------------------------+ 10. template <typename T> 11. T Sum(T arg1, T arg2) 12. { 13. Print(__FUNCTION__, "::", __LINE__); 14. return arg1 + arg2; 15. } 16. //+------------------------------------------------------------------+
Code 01
In code 01, which was discussed in the previous article, we have an opportunity to use the same function for working with different types of data. However, there is something annoying here, to put it mildly. The problem is that in lines 06 and 07 we have data of different types. As you may have noticed after studying the contents of the previous article, the compiler copes with this problem perfectly by creating overloaded functions. So that, in the end you do not need to determine which data type is being used. But this is where the unpleasant part lies: the data types used here must be the same, that is, if the first argument being passed to Sum function template is of float type, then the second argument MUST also be of float type. Otherwise, the compiler will print an error or, at best, a warning that there is a problem between the data types used.
For a better understanding, let us change one of the lines. This can be line 06 or 07 of code 01, so different types will be passed to the function. Remember that the function does not actually exist yet. The compiler must build it based on the provided template. Thus, we have changed code 01 as shown below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(Sum(10, 25)); 07. Print(Sum(5, 35.2)); 08. } 09. //+------------------------------------------------------------------+ 10. template <typename T> T Sum(T arg1, T arg2) 11. { 12. Print(__FUNCTION__, "::", __LINE__); 13. return arg1 + arg2; 14. } 15. //+------------------------------------------------------------------+
Code 02
Please note that only the values that were passed to the function have been changed. In this case, as you can see we decided to change line 07, by comparing code 01 with code 02. However, despite this small and innocent change in the code, look at what happened when we tried to compile it.
Figure 01
The message data shown in image 01 may vary slightly depending on a specific case. The reader may get confused by noticing that the compiler failed to establish a reference base, since the only change was values in line 07 of the code. This is exactly the irritant that we talked about earlier. Since our example is purely didactic, one might be thinking, "Why can't the compiler generate code based on the template provided?" The reason is that since the first argument is an integer, we cannot place a floating-point value in the second argument. Or vice versa, when we first put a floating-point value and then an integer value.
Since Sum function template is defined in code 02, the compiler fails to generate a suitable function to do what is expected from the template function, i.e. from Sum function. Many beginners eventually give up and choose a different approach to solving the problem, when everything could be solved very simply, instead. However, in order to solve this issue correctly, one must first understand what is expected from the function or procedure used as a template in order to create other overloaded procedures or functions.
Since our goal is purely didactic, the function we use is very simple. All we expect from it is to add two values and return the result of this sum. If you have already tried to add a floating-point value to an integer value, then remember that the result will be a floating-point value.
This kind of thing is known as type conversion, or typecasting, and we talked about it when we discussed variables and constants. Basically, what we get is the following:
Figure 02
Based on image 02, we know that double type is used in all cases when mathematical operations are performed with different types, as it is done in line 07 of code 02. Knowing this and knowing that the compiler uses a template rather than an overloaded function, we can make the compiler understand that we are aware of what is happening. Thus, we can change code 02 so that it actually works, as shown in the implementation below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(Sum(10, 25)); 07. Print(Sum((double) 5, 35.2)); 08. } 09. //+------------------------------------------------------------------+ 10. template <typename T> T Sum(T arg1, T arg2) 11. { 12. Print(__FUNCTION__, "::", __LINE__); 13. return arg1 + arg2; 14. } 15. //+------------------------------------------------------------------+
Code 03
Please note that in code 03 we use the same code as in code 02. However, here we are forcing the compiler to understand that we are aware that we are dealing with double type, even if the value is an integer type variable. This explicit type conversion, performed by adding a term to line 07 from code 03, distinguishes the same line 07 from code 02 and allows the compiler to use Sum template function in such a way as to create the appropriate overload. Due to this, the ultimate result will be as follows:
Figure 03
The fact is that the solution we have presented is not the only possible one. We can do something a little bit differently and get the same result. The main thing is to understand exactly what we want to implement and what our goal is at the moment. So instead of just memorizing the code, or worse, using the old CTRL+C and CTRL+V tactics, we want to really understand the concepts used. This is the only way we can solve all programming-related problems as they arise.
Another solution is to limit one of the arguments to a specific type. Although many people do not consider this method suitable, it allows us to solve various types of problems in very specific situations when we know in advance that we will always use a certain type of information in a particular argument.
So, assuming that the first argument will ALWAYS have the integer type, we can do something like this:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. Print(Sum(10, 25)); 07. Print(Sum(5, 35.2)); 08. } 09. //+------------------------------------------------------------------+ 10. template <typename T> T Sum(short arg1, T arg2) 11. { 12. Print(__FUNCTION__, "::", __LINE__); 13. return arg1 + arg2; 14. } 15. //+------------------------------------------------------------------+
Code 04
The result of this code is the same as that of code 03, i.e. image 03. However, in code 04, we tell the compiler that one of the arguments to the template function must always be of type integer. In this case, we use a signed 16-bit type, but it can be any other type. Note that in this situation, we do not want to explicitly perform type conversion. This is because the compiler already knows what to do in this situation. However, it is important to emphasize that the second argument will be determined by the compiler at the stage of creating the executable file. Therefore, we will have one function to respond the call in line 06, where we use only integer types, and another function to respond in line 07, where we use integer type and floating point.
Now, I will ask you: isn't it fun to play and practice in such scenarios? Please note that we tell the compiler how to work. And in this way, we manage to effortlessly create different possibilities for developing the same thing.
But do not think it is over. There is another way to solve this problem. However, in this case, things get a little more complicated, and a different kind of problem arises, which solution will be shown another time. Nevertheless, it really deserves attention, as it is suitable for various situations and may even open the doors to a new world of opportunities for using and working with MQL5.
However, in order not to get confused, we will discuss this in another topic. First, learn these concepts and only then try to adopt what will happen next.
One template, multiple types
In the previous topic, we discussed how to deal with a rather unpleasant problem that sometimes limits us in using the template. However, the studied material is only the first part of what we can actually do. What we are going to discuss here is not very common, at least in MQL5, because so far I do not remember anyone using a similar methodology. Many people may think that such an opportunity does not exist or cannot be realized, which means that their hands will be tied and they will not be able to achieve their main goal.
However, the fact that you have not seen or heard of using something does not mean that it does not exist or that it is not accepted in a programming language. In many cases, the problem lies not even in this, but in other types of problems that may arise precisely because of misuse or (as happens in most cases) due to misunderstanding of some concepts related to a programming tool.
In the previous article, we explained that the T used in the template is actually an identifier that the compiler will use to locally identify a specific type. This is necessary in order to know how to work with incoming information. If you understand identifier’s concept, then you know that if the identifier is declared correctly, it will act like a variable or a constant.
In the case of the identifier, which is the T that we see in line 10 of code 04, it is NOT A VARIABLE because it cannot be changed after identification. "Then obviously it is a constant." However, it is a compiler-defined constant representing the expected data type.
Please note: when talking about the data type, we are referring to the contents from image 02. Therefore, it is important to understand the concept rather than memorize formulas or implementation models. By understanding this concept, which, although being simple, is (as you will soon notice) very powerful, we can create an almost magical solution that understands how a template should be used to create something, be it a function or a procedure. To do this, you will need to add as many identifiers as necessary to cover as many allowed cases as possible. This decision will be yours at the code implementation phase.
Then we can change code 04 and find something more similar to code 02, but without the problem found in code 02, in which only one data type could be used. It sounds complicated, but it is actually much simpler than you might imagine. Let us see what a solution to this problem would look like with the application of a new concept of using multiple type identifiers.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print(#X, " => ", X, "\n"); 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. PrintX(Sum(10, 25)); 09. PrintX(Sum(5, 35.2)); 10. } 11. //+------------------------------------------------------------------+ 12. template <typename T1, typename T2> T1 Sum(T1 arg1, T2 arg2) 13. { 14. Print(__FUNCTION__, "::", __LINE__); 15. 16. return (T1)(arg1 + arg2); 17. } 18. //+------------------------------------------------------------------+
Code 05
This is where things get really complicated, despite the fact that we were careful and showed everything gradually. However, the problem here is precisely in what we do in line 12, where the template for the Sum function is declared.
After executing, code 05 will lead to the result from the image below.
Image 04
We have highlighted one point in this image precisely in order to attract reader's attention. Please, note that the result of the operation is incorrect. Rather, it DOES NOT MATCH the expected value, since the expected value here would be the same as in image 03. But why? We can assume that the case is in the way line 12 is noted, because it obviously does not make any sense. However, the problem is NOT IN LINE 12, as you might assume, but in line 09 or line 16, depending on how you analyze the code or how it should have been implemented. Therefore, it is important to understand accepted concepts and always think before coding haphazardly.
You do not have to believe me. So, WE WILL NOT TOUCH LINE 16, but we will amend line 09 by changing the order in which the values are declared. This leads us to the code provided below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print(#X, " => ", X, "\n"); 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. PrintX(Sum(10, 25)); 09. PrintX(Sum(35.2, 5)); 10. } 11. //+------------------------------------------------------------------+ 12. template <typename T1, typename T2> T1 Sum(T1 arg1, T2 arg2) 13. { 14. Print(__FUNCTION__, "::", __LINE__); 15. 16. return (T1)(arg1 + arg2); 17. } 18. //+------------------------------------------------------------------+
Code 06
Please note that we have changed only what we have described, and the result of code execution can be seen below.
Image 05
"What a crazy and senseless act! Now I am afraid to take up programming until I understand the issue better. It all seemed very easy to me. I even considered myself a programmer, because I was able to write small snippets of code that worked. But looking at it, I realize that I still do not know absolutely anything. I am just starting to learn what it means to be a real programmer."
Calm down, after all it is not that bad. In fact, everything is not always as simple as it seems at first glance, especially when we get into our comfort zone and do not leave it. But the problem is that many people consider themselves good programmers and, because of this, simply stop learning. Therefore, I confirm:
A good programmer NEVER STOPS LEARNING. He is constantly updating knowledge and exploring new concepts. ALWAYS.
Things like what you just saw actually DESTROY a programmer's morale, especially when he gets his hands on code that is, principally, correct. And it is essentially correct. However, it always returns incorrect results for no obvious reason. Therefore, do not fool yourself: it is not enough to be able to write code to be considered a programmer. To reach this level, you have to go through a lot. And in many cases only time will teach you. I have been through this myself, and I am telling you this: it took me a long time to learn how to deal with such issues. I even started to understand why the code might work at one moment and go crazy at another, at first glance producing meaningless results.
But let us figure out what happens in both code 05 and code 06, because they are the same thing: the only difference is a simple change in the sequence of parameter declaration in line 09.
When the compiler encounters the template call that we declare in line 12, it checks which data type is used in each argument, just as before, creating an overloaded function to serve that particular call, in case none of the previously created functions can serve the current template.
Since we have two typename in line 12 to designate two different types, we can use two completely different data types, which was previously impossible. This covers a large number of cases perfectly, because, in fact, values can be either integer or floating-point, and we do not have to worry about it at the very beginning. However, due to specific problems, such simulation does not cover all cases, but this is beyond the scope of today's topic. Knowing that we can use both integer and floating-point data simultaneously and without problems, we can get an almost perfect template.
Thus, the compiler will place the data type declared in the first argument into constant T1, while the data type used in the second argument will be placed into constant T2. Then, having performed type conversion in line 16 to match the returned type of Sum function, we can get a result similar to that shown in images 04 or 05.
Since, looking at template code, we have only a vague idea of what the compiler will actually assemble, it is difficult to understand why we got such different results just by simply modifying the order in which values are declared in line 09.
For a better understanding, let us see what the function written by the compiler will be in both cases. Then, if assuming that we are not using a template, but traditional encoding, we will get the following as code 05:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print(#X, " => ", X, "\n"); 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. PrintX(Sum(10, 25)); 09. PrintX(Sum(5, 35.2)); 10. } 11. //+------------------------------------------------------------------+ 12. int Sum(int arg1, int arg2) 13. { 14. Print(__FUNCTION__, "::", __LINE__); 15. 16. return (int)(arg1 + arg2); 17. } 18. //+------------------------------------------------------------------+ 19. int Sum(int arg1, double arg2) 20. { 21. Print(__FUNCTION__, "::", __LINE__); 22. 23. return (int)(arg1 + arg2); 24. } 25. //+------------------------------------------------------------------+
Code 07
When executing code 07, you will get exactly what is shown in image 04. And code 06, if it had been created in a traditional programming model, would have had as its internal content what can be seen below.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #define PrintX(X) Print(#X, " => ", X, "\n"); 05. //+------------------------------------------------------------------+ 06. void OnStart(void) 07. { 08. PrintX(Sum(10, 25)); 09. PrintX(Sum(35.2, 5)); 10. } 11. //+------------------------------------------------------------------+ 12. int Sum(int arg1, int arg2) 13. { 14. Print(__FUNCTION__, "::", __LINE__); 15. 16. return (int)(arg1 + arg2); 17. } 18. //+------------------------------------------------------------------+ 19. double Sum(double arg1, int arg2) 20. { 21. Print(__FUNCTION__, "::", __LINE__); 22. 23. return (double)(arg1 + arg2); 24. } 25. //+------------------------------------------------------------------+
Code 08
Just as code 07 would result in image 04, code 08 would result in image 05. Please note that the difference between one code and another is very subtle, and in many situations it can go unnoticed, and we will not even realize that something went wrong. However, unlike the code that uses templates, in codes 07 and 08 we will quickly identify the cause of the problem. Looking at the results, you can see that there is an explicit typecasting here, which causes the code to deliver an erroneous response, thereby correcting the problem.
However, when using a template, it is not so easy to notice. In this case, it will be difficult for you and, perhaps, you will even refuse to continue this path. And only if you are strong-willed enough you will eventually figure out where the mistake is. However, before you think, "So, all we want to do is use explicit typecasting to double type in line 16 of code 05 or code 06. This would certainly solve our problem with the response received in the terminal."
Let us see why this does not actually solve the problem. In some cases, it will even create other problems. And the reason for this is quite simple. The problem is that you will try to solve the result in line 09 of the code, forgetting that there is line 08 where we use integer type data. Then, instead of fixing the problem, you can further confuse the situation, thereby creating a new problem elsewhere.
This is certainly a typical situation, which eventually becomes quite awkward. Therefore, it is rare to find practical code that uses a template with multiple types, at least in MQL5. As for the C language, mostly C++, such things are very common and happen all the time.
Therefore, what is presented here is rather interesting than useful for everyday use. However, when it becomes necessary to use it, you will quickly remember what potential problems it can create. So feel free to come up with a solution to resolve such a conflict. As far as I know, there is no easy way to solve this problem. Even in C++, where this happens, you often have to extricate yourself to come round the problem. And believe me, it is not fun at all.
Final considerations
This article explained how to deal with one of the most irritating and difficult programming situations you can encounter: using different types in the same function or procedure template. Although we have spent most of our time focusing only on functions, everything covered here is useful and can be applied to procedures, both when using passing by value and in the cases when we use passing by reference. However, in order not to make the material tedious, we will not show these cases in action.
So let us agree that you should practice and try to formulate small code snippets to explore those cases that we talked about a while ago, such as working with step-by-step links and using procedure templates, because at this early stage we will not necessarily work with that.
In the application you will find many of the codes provided today. The codes that will not be available are simple modifications of the codes provided in the application. In any case, practicing what is shown here will help to better master the content. In the next article, we will talk more about templates. Therefore, see you very soon!
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/15668
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