- Template header
- General template operation principles
- Templates vs preprocessor macros
- Features of built-in and object types in templates
- Function templates
- Object type templates
- Method templates
- Nested templates
- Absent template specialization
Features of built-in and object types in templates
It should be kept in mind that 3 important aspects impose restrictions on the applicability of types in a template:
- Whether the type is built-in or user-defined (user-defined types require parameters to be passed by reference, and built-in ones will not allow a literal to be passed by reference);
- Whether the object type is a class (only classes support pointers);
- A set of operations performed on data of the appropriate types in the template algorithm.
Let's say we have a Dummy structure (see script TemplatesMax.mq5):
struct Dummy
|
If we try to call the Max function for two instances of the structure, we will get a bunch of error messages, with mains as the following: "objects can only be passed by reference" and "you cannot apply a template."
// ERRORS:
|
The pinnacle of the problem is passing template function parameters by value, and this method is incompatible with any object type. To solve it, you can change the type of parameters to links:
template<typename T>
|
The old error will go away, but then we will get a new error: "'>' - illegal operation use" ("'>' - illegal operation use"). The point is that the Max template has an expression with the '>' comparison operator. Therefore, if a custom type is substituted into the template, the '>' operator must be overloaded in the template (and the structure Dummy does not have it: we'll get to that shortly). For more complex functions, you will likely need to overload a much larger number of operators. Fortunately, the compiler tells you exactly what is missing.
However, changing the method of passing function parameters by reference additionally led to the previous call not working as such:
Print(Max<ulong>(1000, 10000000)); |
Now it generates errors: "parameter passed as reference, variable expected". Thus, our function template stopped working with literals and other temporary values (in particular, it is impossible to directly pass an expression or the result of calling another function into it).
One might think that the universal way out of the situation would be template function overloading, i.e., the definition of both options, that differs only in the ampersand in the parameters:
template<typename T>
|
But it won't work. Now the compiler throws the error "ambiguous function overload with the same parameters":
'Max' - ambiguous call to overloaded function with the same parameters
|
The final, working overload would require the modifier const to be added to the links. Along the way, we added the operator Print to the template Max so that we can see in the log which overload is being called and which parameter type T corresponds to.
template<typename T>
|
We have also implemented an overload of the operator '>' in the Dummy structure. Therefore, all Max function calls in the test script are completed successfully: both for built-in and user-defined types, as well as for literals and variables. The outputs that go into the log:
double Max<double>(double,double) T=double
|
An attentive reader will notice that we now have two identical functions that differ only in the way parameters are passed (by value and by reference), and this is exactly the situation against which the use of templates is directed. Such duplication can be costly if the function body is not as simple as ours. This can be solved by the usual methods: separate the implementation into a separate function and call it from both "overloads", or call one "overload" from the other (an optional parameter was required to avoid the first version of Max calling itself and, resulting in stack overflows):
template<typename T>
|
We still have to consider one more point associated with user-defined types, namely the use of pointers in templates (recall, that they apply only to class objects). Let's create a simple class Data and try to call the template function Max for pointers to its objects.
class Data
|
We will see in the log that 'T=Data*', i.e. the pointer attribute, hits the inline type. This suggests that, if necessary, you can write another overload of the template function, which will be responsible only for pointers.
template<typename T>
|
In this case, the attribute of the pointer '*' is already present in the template parameters, and so type inference results in 'T=Data'. This approach allows you to provide a separate template implementation for pointers.
If there are multiple templates that are suitable for generating an instance with specific types, the most specialized version of the template is chosen. In particular, when calling the function Max with pointer arguments, two templates with parameters T (T=Data*) and T* (T=Data), but since the former can take both values and pointers, it is more general than the latter, which only works with pointers. Therefore, the second one will be chosen for pointers. In other words, the fewer modifiers in the actual type that is substituted for T, the more preferable the template variant. In addition to the attribute of the pointer '*', this also includes the modifier const. The parameters const T* or const T are more specialized than just T* or T, respectively.