Principios generales de funcionamiento de la plantilla

Recordemos la sobrecarga de funciones. Consiste en definir varias versiones de una función con distintos parámetros, incluidas las situaciones en las que el número de parámetros es el mismo, pero sus tipos son diferentes. A menudo, un algoritmo de este tipo de funciones es el mismo para parámetros de distintos tipos. Por ejemplo, MQL5 tiene una función integrada MathMax que devuelve el mayor de los dos valores que se le han pasado:

double MathMax(double value1double value2);

Aunque sólo se proporciona un prototipo para el tipo double, en realidad la función es capaz de trabajar con pares de argumentos de otros tipos numéricos, como int o datetime. En otras palabras: la función es un núcleo sobrecargado para tipos numéricos integrados. Si quisiéramos conseguir el mismo efecto en nuestro código fuente, tendríamos que sobrecargar la función duplicándola con diferentes parámetros, de esta forma:

double Max(double value1double value2)
{
   return value1 > value2 ? value1 : value2;
}
 
int Max(int value1int value2)
{
   return value1 > value2 ? value1 : value2;
}
 
datetime Max(datetime value1datetime value2)
{
   return value1 > value2 ? value1 : value2;
}

Todas las implementaciones (cuerpos de función) son iguales. Sólo cambian los tipos de parámetros.

Aquí es cuando las plantillas resultan útiles. Utilizándolas podemos describir una muestra del algoritmo con la implementación requerida, y el propio compilador generará varias instancias del mismo para los tipos específicos implicados en el programa. La generación se produce sobre la marcha durante la compilación y es imperceptible para el programador (a menos que haya un error en la plantilla). El código fuente obtenido de forma automática no se inserta en el texto del programa, sino que se convierte directamente en código binario (archivo ex5).

En la plantilla, uno o más parámetros son designaciones formales de tipos, para los cuales, en la etapa de compilación y de acuerdo con reglas especiales de inferencia de tipos, se seleccionarán tipos reales de entre los integrados o los definidos por el usuario. Por ejemplo, la función Max puede describirse utilizando la siguiente plantilla con el parámetro de tipo T:

template<typename T>
T Max(T value1T value2)
{
   return value1 > value2 ? value1 : value2;
}

Y a continuación, se puede aplicar para variables de diversos tipos (véase TemplatesMax.mq5):

void OnStart()
{
   double d1 = 0d2 = 1;
   datetime t1 = D'2020.01.01', t2 = D'2021.10.10';
   Print(Max(d1d2));
   Print(Max(t1t2));
   ...
}

En este caso, el compilador generará automáticamente variantes de la función Max para los tipos double y datetime.

La plantilla en sí no genera código fuente. Para ello es necesario crear una instancia de la plantilla de un modo u otro: llamar a una función de plantilla o mencionar el nombre de una clase de plantilla con tipos específicos para crear un objeto o una clase derivada.

Hasta que no se haga esto, el compilador ignorará todo el patrón. Por ejemplo, podemos escribir la siguiente función supuestamente de plantilla, que en realidad contiene código sintácticamente incorrecto. Sin embargo, la compilación de un módulo con esta función tendrá éxito siempre y cuando no sea invocada en ninguna parte.

template<typename T>
void function()
{
  it's not a comment, but it's not source code either
   !%^&*
}

Para cada uso de la plantilla, el compilador determina los tipos reales que coinciden con los parámetros formales de la plantilla. A partir de esta información se genera automáticamente el código fuente de la plantilla para cada combinación única de parámetros. Esta es la instancia.

Así, en el ejemplo dado de la función Max, hemos llamado a la función de plantilla dos veces: para el par de variables del tipo double, y para el par de variables del tipo datetime. El resultado fueron dos instancias de la función Max con código fuente para las coincidencias T=double y T=datetime. Por supuesto, si se llama a la misma plantilla en otras partes del código para los mismos tipos, no se generarán nuevas instancias. Sólo se necesita una nueva instancia de la plantilla si ésta se aplica a otro tipo (o conjunto de tipos, si hay más de 1 parámetro).

Tenga en cuenta que la plantilla Max tiene un parámetro, y establece el tipo para dos parámetros de entrada de la función y su valor de retorno a la vez. En otras palabras: la declaración de plantilla es capaz de imponer ciertas restricciones a los tipos de argumentos válidos.

Si llamáramos a Max en variables de tipos diferentes, el compilador no sería capaz de determinar el tipo para crear una nueva instancia de la plantilla y mostraría el error «parámetros de plantilla ambiguos, debe ser 'double' o 'datetime'»:

Print(Max(d1t1)); // template parameter ambiguous,
                    // could be 'double' or 'datetime'

Este proceso de descubrimiento de los tipos reales de los parámetros de la plantilla basándose en el contexto en el que se utiliza la plantilla se denomina deducción de tipos. En MQL5, la inferencia de tipo está disponible sólo para las plantillas de métodos y funciones.

Para las clases, estructuras y uniones se utiliza una forma diferente de vincular tipos a los parámetros de la plantilla: los tipos requeridos se especifican explícitamente entre paréntesis angulares al crear una instancia de plantilla (si hay varios parámetros, el número correspondiente de tipos se indica como una lista separada por comas). Para obtener más información, consulte la sección Plantillas de tipos de objeto.

El mismo método explícito puede aplicarse a las funciones como alternativa a la inferencia automática de tipos.

Por ejemplo, podemos generar y llamar a una instancia de Max para el tipo ulong:

Print(Max<ulong>(100010000000));

En este caso, si no fuera por la indicación explícita, la función de plantilla se asociaría al tipo int (basado en los valores de las constantes enteras).