
Del básico al intermedio: Plantilla y Typename (IV)
Introducción
El contenido expuesto aquí tiene un propósito puramente didáctico. En ningún caso debe considerarse una aplicación final, cuyo objetivo no sea el estudio de los conceptos mostrados aquí.
En el artículo anterior, "Del básico al intermedio: Plantilla y Typename (III)", empezamos a hablar de un tema que muchos principiantes encuentran especialmente complicado. Esto se debe al simple hecho de que muchos no lo entienden o no recibieron la orientación adecuada sobre un concepto muy importante para los programadores de MQL5: el concepto de plantilla. Como sé que muchas de las personas que leen estos artículos no saben nada o muy poco de programación, intento hacer el material lo más didáctico posible.
Por esta razón, en el artículo anterior terminamos de forma algo brusca, ya que lo concluimos con una imagen de error y un código que no generaba un archivo ejecutable. Sé que a muchos les puede decepcionar ver este tipo de cosas en un artículo. Sin embargo, apenas había comenzado a introducir un tema que resulta bastante complicado en el primer contacto: la sobrecarga de tipos. De hecho, no estamos creando una sobrecarga de tipos, sino un tipo de plantilla que permite al compilador generar el tipo adecuado para cada situación que necesitamos manejar.
Como, en principio, todo código que se vea en un artículo debería funcionar, tenemos una pequeña limitación en la explicación. Sin embargo, estoy intentando explicarlo de manera que tú, mi querido lector, entiendas que un código no siempre funciona cuando lo implementamos. He visto a mucha gente queriendo aprender a resolver problemas en sus códigos. Sin embargo, en la gran mayoría de los casos, si no en casi todos, el problema radica en que la persona interesada en crear una aplicación determinada no posee los conceptos adecuados sobre ese recurso o ese lenguaje de programación. Y, sin entender debidamente ciertos conceptos, es difícil explicar cómo lidiar con ciertos tipos de problemas que, para los programadores profesionales, son solo pequeños detalles que resolver, pero que para un principiante suponen un gran problema.
Hasta ahora, hemos tratado la sobrecarga de funciones y procedimientos mediante una plantilla. Pero la cosa se complica un poco cuando lo aplicamos a otros tipos de aplicación, como se mostró al final del artículo anterior. Por ello, vamos a empezar desde ese punto, pero en un nuevo tema que empieza justo debajo.
Cómo usar una plantilla de tipo
Bien, el concepto en sí que necesita aplicarse es simple. "Sin embargo, visualizarlo con el fin de poder aplicar adecuadamente el concepto es algo un poco complicado. Empezaremos por lo que se vio en el artículo anterior, viendo primero el código original, que funciona y se puede compilar. Se muestra justo abajo.
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. //+------------------------------------------------------------------+
Código 01
Cuando este código 01 se compila y ejecuta en MetaTrader 5, se obtiene lo que se muestra a continuación.
Imagen 01
Obviamente, este resultado no es del todo correcto. Esto se debe a que hay una región destacada en esta imagen 01. Pero, dependiendo del caso de uso, este resultado será correcto y adecuado. Sin embargo, no es esto lo que queremos. Lo que queremos es que el valor declarado en la línea 33 de este código tenga dos bytes de ancho. No obstante, debido precisamente a que la unión utiliza ocho bytes de ancho, estamos obligados a utilizar este tipo de declaración, lo que hace que el resultado final sea completamente inadecuado, como se puede ver en la imagen 01.
Sin embargo, en el artículo anterior ya creamos directamente una plantilla de tipo. Aunque el código que vimos allí no estuviera errado, sino que le faltara algo, es difícil explicar por qué las cosas necesitan hacerse de la manera en que las haremos. Así que decidí terminar el artículo en ese punto y dejar que tú, mi querido y estimado lector, reflexionaras y estudiaras el tema con calma. El objetivo es intentar entender por qué el código no funcionaba. No obstante, aquí vamos a entender cómo proceder de manera adecuada y, por tanto, por qué el código necesita implementarse de una forma muy específica para que el compilador entienda qué es lo que hay que hacer.
Entonces, el siguiente paso natural es cambiar el código 01 por otro ligeramente diferente. Esto se hace antes de alcanzar lo que se vio en el artículo anterior. Así surge el código que se muestra a continuación.
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. //+------------------------------------------------------------------+
Código 02
Vale, cuando ejecutamos este código 02, el resultado final es lo que podemos visualizar en la imagen justo abajo.
Imagen 02
Observa que en esta imagen 02 tenemos exactamente lo que buscábamos conseguir. Es decir, ahora tenemos un valor de un tipo que se muestra y que necesita ocho bytes. Y otro valor que también se muestra y que requiere dos bytes, tal y como se esperaba. No obstante, observa cómo se ha hecho. Aunque funcione, estamos realizando un sobreesfuerzo al crear código y ajustar las cosas de manera adecuada, lo que aumenta la posibilidad de error a medida que el código crece y se vuelve más complicado al necesitar añadirle cada vez más cosas.
Vé que estamos haciendo algo muy simple y, aun así, el código ha comenzado a volverse algo confuso. Es en este punto cuando surge la idea de utilizar plantillas de tipo. La razón es que la única diferencia entre el bloque de código entre las líneas 15 y 29, y el bloque de código entre las líneas 31 y 45, es el tipo que se define dentro de la unión. Esto puede observarse en las líneas 18 y 34 del código 02.
Por este motivo, comenzamos a crear la plantilla, que tiene como objetivo principal simplificar este mismo código 02, ya que se está produciendo una sobrecarga en la unión que debe realizarse. Así, aplicando el concepto mostrado y utilizado para generar una plantilla de una función o procedimiento. Que, en este caso, podría sobrecargarse. Llegamos finalmente al código que se muestra a continuación.
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. //+------------------------------------------------------------------+
Código 03
Y es aquí donde la cosa comienza a volverse algo confusa. Esto se debe a que el código 03 intenta crear lo que se ve en el código 02, pero utilizando algo muy similar a lo que se muestra en el código 01. Sin embargo, al compilar este código 03, el compilador devuelve el siguiente mensaje de error.
Imagen 03
Definitivamente hay algo mal aquí, pero a primera vista no tiene el más mínimo sentido. Porque, a primera vista, estás declarando correctamente la plantilla. Entonces, ¿qué es lo que falla en este código 03 que impide que se compile? Si no se compila, es porque el compilador no entiende lo que necesita hacerse.
Bien, mi querido lector, sé que muchos se aferran a ciertas cosas y siempre intentan resolver los problemas que el compilador nos irá informando e indicando. Esto es algo primordial. Sin embargo, en este caso, estos mensajes mostrados por el compilador, que pueden verse en la imagen 03, no nos indican cuál es el problema.
Sin embargo, existe un concepto que expliqué en los primeros artículos sobre variables y constantes. Este concepto es muy importante en este preciso momento para que podamos comprender cómo lidiar con este tipo de situaciones. Como puedes ver, en la línea siete del código 03 estamos declarando una variable. ¿Cierto? Solo que te pregunto: ¿Qué tipo de variable estamos declarando en la línea siete? Esa es la cuestión. Si ni yo ni ningún otro programador lo sabemos, ¿cómo lo sabrá el compilador?
Vale, puedes decirme: «En la línea 25 digo que quiero un tipo que posea ocho bytes de ancho y en la línea 35, dos bytes». Cierto. Pero esto no le dice al compilador qué tipo de variable debe utilizarse. Observa que en las líneas 25 y 35 NO ESTAMOS DECLARANDO LA VARIABLE. Lo que estamos haciendo es asignarle un valor. La declaración se hace en las líneas 23 y 33. ¿Ves ahora el problema?
Bien, pero existe otro problema aún mayor que es, precisamente, lo que causa la generación de todos aquellos mensajes de error que pueden verse en la imagen 03: el hecho de que, a pesar de que la declaración se hace en las líneas 23 y 33, se está declarando algo que es desconocido para el compilador, es decir, el tipo de variable de la línea siete. Observa que las líneas 23 y 33 hacen referencia al tipo declarado en la línea cinco. Es decir, una unión.
No obstante, esta unión es una plantilla. Por tanto, el tipo de dato que se utilizará lo definirá el compilador. Como no sabe qué tipo de dato se debe utilizar, se disparan todos esos mensajes de error. Pero ahora viene el concepto que se va a adoptar. Si has comprendido cómo se utiliza la plantilla en funciones y procedimientos, sabrás que la declaración de la variable se hace en algún punto. Cuando la función o el procedimiento se sobrecargan, el compilador ya conoce el tipo de dato, por lo que puede crear la rutina adecuada.
Entonces, sabiendo esto, puedes preguntarte: ¿cómo le digo al compilador qué tipo de dato debo utilizar? Normalmente, utilizamos el tipo de dato seguido del nombre de la variable. Y, ya que estamos haciendo esto en las líneas 23 y 33 del código 03, no tengo ni idea de cómo resolver este problema. Si has llegado a este punto y has entendido todos estos conceptos, es hora de ver cómo solucionar este problema. Para poder utilizar plantillas y sobrecargar tipos diferentes. Para ello, MQL5 hace uso de una declaración especial de variables que pueden ser locales o globales, que ya existe en los lenguajes C y C++. Pero recuerda: lo importante es entender el concepto, no simplemente memorizar cómo hacer las cosas. La solución se encuentra justo debajo.
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. //+------------------------------------------------------------------+
Código 04
Observa lo que estamos haciendo en este punto se trata de un cambio muy sutil. Sin embargo, cuando intentes compilar este código 04, observa lo que sucede.
Imagen 04
Es decir, algo aparentemente insignificante que no tiene mucho sentido en este momento permite que el código se compile. El resultado de la ejecución de este código 04 es lo que podemos ver en la imagen siguiente.
Imagen 05
Qué cosa más linda y maravillosa, ¿no crees, querido lector? Pero, ¿qué ocurrió aquí? ¿Por qué este código 04 funciona y el código 03 no? ¿Y por qué hacemos esta declaración extraña en las líneas 23 y 33? Ahora estoy igual que un bote hinchable en el agua. Estoy flotando. Porque no entiendo nada de lo que está pasando aquí.
De acuerdo, querido lector, veamos qué ocurrió aquí y por qué la declaración debe hacerse como se hace en las líneas 23 y 33. Muy bien, este es el cuarto artículo que publico sobre plantillas y typename. En el segundo artículo, vimos que podíamos forzar al compilador a utilizar un tipo de dato u otro para que el tipo se ajustara al parámetro que recibiría una función o procedimiento. En aquel caso, la conversión de tipo se producía durante la asignación del valor. Para ello, utilizábamos una conversión de tipo explícita, colocando el tipo de destino entre paréntesis. Es algo muy común en diversos momentos, como ya habrás notado en otros códigos vistos hasta ahora. Sin embargo, una cosa es asignar un valor a una variable ya declarada. Otra cosa es asignar un tipo a una variable aún no declarada.
Como las plantillas buscan crear precisamente un modelo de función, procedimiento o tipo de dato que podamos utilizar en el futuro, estarán acompañadas de la declaración typename. Y aquí radica la cuestión. Como expliqué en artículos anteriores, la T que acompaña a la declaración typename es, en realidad, una etiqueta para definir algo posteriormente. Por tanto, cuando el compilador sustituye la T, obtendremos la definición del tipo que necesitamos utilizar. Y el compilador podrá crear el código de manera adecuada. Por tanto, cuando en las declaraciones de las líneas 23 y 33 declaramos un tipo de la forma en que lo estamos haciendo, en realidad le estamos diciendo al compilador cuál es el tipo de dato que se debe utilizar en lugar de la T que acompaña a la declaración typename.
Como aún no he mostrado cómo utilizar esta declaración de forma más profunda, es posible que no lo entiendas de inmediato. Sin embargo, como puedes ver, funciona. Por eso, la declaración debe hacerse de esta manera.
Bien, si te ha parecido divertido, te va a encantar lo que viene a continuación. El motivo es que ahora vamos a convertir la macro de este código 04 en una función o procedimiento. El objetivo es que el código 04 siga funcionando y se obtenga el resultado que se ve en la imagen 05. Pero como esto implica aplicar lo explicado aquí de una forma más avanzada, vamos a tratar este tema por separado.
¿Por qué complicar, si podemos simplificar?
Muchos de ustedes pensarán que lo que estamos haciendo aquí es una locura, que este tipo de material es muy avanzado y que no necesitamos aprender a hacerlo. De hecho, debo estar de acuerdo con esta opinión. Y es que, sabiendo solo cómo crear funciones y procedimientos, declarar variables y utilizar algunos comandos básicos, podemos crear prácticamente cualquier cosa. Sin embargo, aunque es cierto que, cuantas más herramientas y recursos tenemos a nuestra disposición, más fácil es implementar y construir las cosas. Incluso puedes construir un silo de almacenamiento de granos con un solo clavo. Y esto ya se ha hecho. Pero sería mucho más sencillo si pudieras utilizar más recursos, como clavos.
Pues bien, lo que vamos a hacer aquí y ahora es crear una función que sustituya a la macro definida en la línea 14 del código 04. Esto sí que será divertido e interesante. Pero antes de hacerlo, debo recordarles a todos ustedes, queridos y estimados lectores, que el objetivo es la enseñanza. Antes de intentar entender lo que se hará aquí, debes entender lo que se hizo en el tema anterior. Solo así las cosas tendrán sentido.
De acuerdo. Entonces, comenzaremos con la idea inicial. Es decir, eliminar la macro del código 04 e intentar convertirla en una función. Pero antes de hacer esto, vamos a crear un procedimiento que, en mi opinión, es más sencillo de entender. Entonces, tú, todo feliz por haber aprendido algo nuevo, vas y creas el siguiente código que se ve justo abajo.
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. //+------------------------------------------------------------------+
Código 05
Al intentar compilar este código 05, para tu sorpresa y decepción, se muestra el siguiente resultado, que puedes ver justo debajo.
Imagen 06
¿Otra vez esta clase de cosas? Qué cosa más molesta y sin gracia. Calma, querido lector, calma. No hay motivo para el pánico o la desesperación. Todavía no. El problema es muy similar al que se planteaba en el tema anterior. Pero la solución es diferente en este caso. Presta atención al siguiente hecho: en las líneas 19 y 29 se hace una llamada al procedimiento de la línea 34. Hasta aquí, todo bien. El problema es que el procedimiento espera precisamente el tipo de dato que se está definiendo en la línea cinco. Y como este tipo es una plantilla, el compilador no sabe cómo lidiar con esto, ya que no podemos indicarle qué tipo de dato debe utilizarse.
Pero espera un momento. ¿Cómo así? Estamos declarando el tipo de dato en las líneas 14 y 24. Sí, querido lector. Sin embargo, en estas líneas 14 y 24, estás definiendo localmente el tipo de dato que se va a utilizar. No obstante, esto no se pasa al procedimiento de la línea 34, precisamente porque es un tipo complejo y no primario, como ocurría cuando se pasaban las cosas como se hacía antes. Además, hay otro pequeño detalle. Como el tipo de dato permite la sobrecarga de tipo, al intentar pasar esto dentro de una función o procedimiento, debemos asegurarnos de que la función o procedimiento TAMBIÉN PUEDA SER SOBRECARGADO. Por eso dije que este tipo de cosa es extremadamente divertida.
Es decir, dado que el argumento puede ser sobrecargado dentro del procedimiento OnStart, la función o procedimiento que reciba este mismo tipo de dato también debe poder ser sobrecargado. Así, todo quedará adecuado y el compilador podrá comprender el código para crear el ejecutable deseado.
Entonces, habiendo entendido esto y sabiendo ya cómo crear una función sobrecargada que utilice plantilla, tomas el código 05 y lo modificas hasta crear el código 06, que podemos ver justo debajo.
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. //+------------------------------------------------------------------+
Código 06
Genial. Ahora sí que tenemos un código adecuado. El compilador finalmente podrá entender lo que estoy intentando hacer. Así que, una vez más, feliz y satisfecho, pides al compilador que cree el ejecutable, ya que la sobrecarga utilizando plantilla se ha implementado, como puedes observar en este código 06. Sin embargo, de manera extremadamente decepcionante, el compilador te informa de lo que se ve justo abajo.
Imagen 07
Ahora sí, definitivamente este sistema está tomándome el pelo. No puede ser otra cosa. No consigo entender. Por qué razón DIOS ha sido tan cruel conmigo, haciéndome intentar crear algo que yo podría hacer de otra manera. Calma, mi querido lector. Ten calma. Lo que estamos haciendo es algo que hace que mucha gente cree códigos siempre de la misma manera, convirtiendo cosas que podrían hacerse de formas mucho más simples y con menos posibilidades de error en algo monstruoso y con repetición de varios puntos que podrían mejorarse.
De hecho, crear una plantilla no es una de las tareas más sencillas. Por esta razón, es difícil que veas códigos que hagan uso de este tipo de recurso. Sin embargo, entenderlo te ayudará en muchos momentos, ya que hace que el código sea considerablemente más sencillo desde el punto de vista de la programación, ya que traslada toda la complejidad para que el compilador pueda lidiar con ella. Ahora, vamos a entender por qué está ocurriendo este error. Al principio, este tipo de cosas hace que uno sufra bastante. Luché durante mucho tiempo hasta aprender a hacerlo de forma correcta, ya que este tipo de cosas se utiliza mucho en C y C++. Como MQL5 se basa en muchos conceptos y cosas vistas en C y C++, una vez que aprendí a trabajar con estos lenguajes, aprender MQL5 fue pan comido. Casi como un juego de niños. Pero aprender lo que estoy explicando aquí fue bastante complicado.
Presta atención. Aunque el compilador informe de que el error está en las líneas 19 y 29, nos está llevando al lugar equivocado. No es culpa del compilador, sino nuestra. Ya voy a explicar el motivo. En el tema anterior, ¿recuerdas que fue necesario tomar medidas para que el compilador pudiera entender qué tipo de datos estábamos utilizando? Hicimos una declaración que se puede ver replicada en este código 06, en las líneas 14 y 24.
Pues bien, aunque esto se está haciendo de forma correcta en el procedimiento OnStart, no está ocurriendo lo mismo en el procedimiento Swap. Incluso podrías pensar que la declaración de la plantilla del procedimiento Swap está correcta. Sin embargo, no es del todo correcta. Esto se explicó en los tres artículos anteriores. No obstante, es difícil darse cuenta del error aquí. Pero si no consigues ver dónde está el error es porque aún no has logrado entender cómo una plantilla de función o procedimiento consigue sobrecargar la misma.
Observa que en la declaración de la plantilla, en la línea 34, indicamos que vamos a utilizar el tipo T para crear la sobrecarga, ¿correcto? Ahora, mira nuevamente la línea 35. ¿Qué argumento, que en este caso es solo uno, está recibiendo este tipo T? No existe ningún argumento que reciba este tipo T. Es decir, a pesar de que la declaración informa de que estamos creando una plantilla, en realidad no se está utilizando. Vuelve ahora a los artículos anteriores y comprueba cómo se hacía la declaración. Verás algo parecido a lo que se muestra justo abajo.
. . . 15. template <typename T> 16. T Averange(const T &arg[]) . . .
Fragmento 01
Mira, en este fragmento 01, el siguiente hecho: el tipo T está siendo declarado en la línea 15, pero, justo después, lo estamos usando en la línea 16. Esto con el fin de que el argumento pueda utilizar el tipo cuando el compilador vaya a crear la función o procedimiento. Si no se hace esto, el compilador no sabría cómo actuar. Y lo mismo ocurre aquí en el código 06. Tenemos la declaración, pero no la utilizamos. En otras palabras, no le estamos indicando al compilador cómo utilizar la declaración. Para ello, debemos modificar de nuevo el código 06 para que el compilador pueda entender lo que estamos intentando hacer. A continuación se muestra el código que funcionará.
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. //+------------------------------------------------------------------+
Código 07
Sin duda, ahora tenemos un código que funciona y que produce el mismo resultado que podemos ver en la imagen 05. Pero te pregunto, querido lector: ¿lograste entender por qué este código 07 funciona, querido lector? Imagina que en una prueba para trabajar como programador te solicitan que crees un código que genere el mismo resultado que el código 07 haciendo uso de una función o procedimiento,
pero sin utilizar una plantilla para ello y necesitando utilizar la plantilla de la unión declarada en la línea cuatro del código 07. ¿Podrías hacerlo? ¿Podrías cumplir esta tarea para conseguir el puesto de programador, o dirías simplemente que tal cosa no sería posible? Ya que, en un momento dado, mencioné que el procedimiento o función, en este caso, necesitaría utilizar una plantilla para poder generarse.
Hay conceptos y detalles que hacen que las cosas sean bastante interesantes. Por ejemplo, podemos crear el código 07 de otra manera para controlar la sobrecarga directamente, sin utilizar una plantilla para ello. Como este tipo de cosas exige una explicación adecuada para poder comprenderla, veremos este tema en el próximo artículo.
Consideraciones finales
En este artículo, hemos visto cómo podemos controlar y crear plantillas de una forma mucho más general y precisa. Como sé que este contenido es muy complicado de comprender de golpe, pues yo mismo pasé por esto cuando estudiaba C y C++ al principio de mi carrera como programador, te pido, querido lector, que tengas paciencia. Te recomiendo que practiques lo que se muestra en estos artículos y, sobre todo, que intentes comprender los conceptos que se presentan. Si los entiendes, podrás crear prácticamente cualquier tipo de código, con muchas menos dificultades que aquellos que insisten en memorizar fragmentos de código y sintaxis de lenguajes de programación. Así que practica y estudia con calma el contenido del anexo. Nos vemos en el próximo artículo.
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/15670
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso