preview
Do básico ao intermediário: Template e Typename (IV)

Do básico ao intermediário: Template e Typename (IV)

MetaTrader 5Exemplos | 28 janeiro 2025, 09:31
33 0
CODE X
CODE X

Introdução

O conteúdo exposto aqui, visa e tem como objetivo, pura e simplesmente a didática. De modo algum deve ser encarado como uma aplicação final, onde o objetivo não seja o estudo dos conceitos aqui mostrados.

No artigo anterior Do básico ao intermediário: Template e Typename (III), começamos a falar de um tema que para muitos iniciantes é um dos mais complicados que existe. Isto por conta do simples fato de que muitos não entendem ou não foram corretamente orientados a respeito de um conceito muito importante para programadores de MQL5. Que é o conceito de template. Como sei que muitos de fato, que estão lendo estes artigos, não sabem nada, ou sabem muito pouco de programação. Estou tentando deixar o material o mais didático quanto for possível ser feito.

Por conta disto, é que no artigo anterior, terminamos as coisas de uma maneira um tanto quanto brusca. Já que terminamos o artigo, com uma imagem de erro. E com um código que não produzia de fato um executável. Sei que muitos podem estar um tanto quanto decepcionados ao ver aquele tipo de coisa sendo posta em um artigo. Porém, ali apenas comecei a introduzir um assunto que realmente é bem complicado, quando temos o primeiro contato com ele. Que é a sobrecarga de tipos. De fato, não estamos criando uma sobrecarga de tipos. Mas sim um tipo de template, que permite ao compilador, gerar um tipo adequado a cada situação que estamos precisando lidar.

Como a princípio, todo e qualquer código visto em um artigo, de fato deveria funcionar. Acabamos tendo uma pequena limitação na explicação. Porém, estou tentando deixar a coisa de uma maneira que você, meu caro leitor, consiga compreender que nem sempre um código funciona quando o implementamos. Tenho visto, muita gente querendo aprender como resolver problemas em seus códigos. Porém, na grande maioria das vezes, se não em quase todas. O problema está no fato de a pessoa interessada em criar uma dada aplicação, não possuir os conceitos adequados sobre este ou aquele recurso. E sem entender devidamente certos conceitos, fica difícil explicar como lidar com certos tipos de problemas, que para programadores profissionais, é apenas um pequeno detalhe a ser resolvido. Mas que para um iniciante é um baita de um problema.

Ok, até este ponto, temos lidado com a sobrecarga de funções e procedimentos, usando para isto um template. Mas a coisa fica um pouco mais complicada quando levamos isto para outros tipos de aplicação. Como o que foi mostrado no final do artigo anterior. Sendo assim, vamos começar daquele ponto. Mas em um novo tópico, que começa logo abaixo.


Entendendo como usar um template de tipo

Bem, como o conceito em si, que precisa ser aplicado é simples. Porém enxergar isto, a fim de conseguir aplicar adequadamente o conceito é algo um pouco complicado. Vamos iniciar com o que foi visto no artigo anterior. Primeiramente vendo o código original, que funciona e pode ser compilado. Este é mostrado logo abaixo.

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

Este código 01, quando compilado e executado no MetaTrader 5, irá produzir o que é visto logo abaixo.

Imagem 01

Obviamente este resultado não está de todo correto. Isto porque temos uma região que está sendo destacada nesta imagem 01. Mas dependendo do caso em que estivermos implementando as coisas. Este resultado visto na imagem 01, estará de fato correto e perfeitamente adequado. Porém não é isto que queremos. O que queremos é que o valor declarado na linha 33 deste código 01, tenha dois bytes de largura. Entretanto devido justamente ao fato de que a união utiliza oito bytes de largura. Ficamos presos a este tipo de declaração. O que torna o resultado final completamente inadequado. Como pode ser visto na imagem 01.

Porém, no artigo anterior, já formos direto criando um template de tipo. Apesar daquele código visto lá, não está de fato errado. Mas sim faltando algo nele. Fica difícil explicar por que as coisas precisam ser feitas da maneira como as faremos. Assim sendo, decidi, terminar o artigo naquele ponto, deixando que você, meu caro e estimado leitor, viesse a pensar e estudar o assunto com calma. Isto para tentar entender por que o código não estava funcionando. No entanto, aqui de fato iremos entender como proceder de maneira adequada. Tornando assim possível entender por que o código precisa ser implementado de uma maneira muito específica, a fim de que o compilador consiga entender o que precisa ser feito.

Então o próximo passo natural é mudar o código 01, para um código ligeiramente diferente. Isto antes de alcançar o que foi visto no artigo anterior. Desta forma, surge o código que é mostrado logo na sequência.

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

Ok, quando executamos este código 02, o resultado final é o que podemos visualizar na imagem logo abaixo.

Imagem 02

Observe que agora, nesta imagem 02, temos exatamente o que procurávamos conseguir. Ou seja, agora temos um valor de um tipo sendo mostrado, que precisa de oito bytes. E um outro valor também sendo mostrado, no caso que necessita de dois bytes. Da forma como era esperado e estimado. No entanto observe como isto foi feito. Apesar de isto funcionar, estamos tendo um sobre trabalho em criar código e ajustar as coisas de maneira adequada. Tornando a possibilidade de erro, algo cada vez maior, conforme o código vai crescendo e se tornando mais complicando, a medida em que precisamos adicionar cada vez mais coisas a ele.

Perceba que aqui estamos fazendo algo muito simples, e ainda assim o código começou a ficar um tanto quanto confuso. É neste ponto que surge a ideia de utilizar templates de tipo. Isto por que, a única e verdadeira diferença entre o bloco de código presente entre as linhas 15 até 29 e o bloco de código entre as linhas 31 e 45 é o tipo que está sendo definido dentro da união. Isto pode ser observado, nas linhas 18 e 34 do código 02.

Devido a isto, partimos para a criação do template, que tem como objetivo principal, simplificar este mesmo código 02. Já que estamos tendo uma sobrecarga da união necessitando ser feita. Assim, usando o conceito mostrado e utilizado para se gerar um template de uma função ou procedimento. Que neste caso poderia ser sobrecarregado. Chegamos finalmente ao código que é mostrado logo abaixo.

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

E é aqui onde a coisa começa a ficar um tanto quanto confusa. Isto por que, este código 03, visa criar o que é visto no código 02. Porém usando algo muito parecido com o que é mostrado no código 01. No entanto, quando você tenta compilar este código 03, irá obter a seguinte resposta do compilador.

Imagem 03

Definitivamente tem algo errado aqui, mas que a princípio não faz o mínimo sentido. Já que à primeira vista, você está declarando corretamente o template. Então o que está errado neste código 03, que o impede de ser compilado. Já que se ele não está sendo compilado, é por conta de que o compilador não está conseguindo entender o que precisa ser feito.

Bem, meu caro leitor, sei que muitos se apegam em muito a certas coisas, e tentam sempre resolver os problemas que o compilador estará nos informando e indicando. Isto é uma coisa primordial. Porém, aqui e neste momento estamos lidando com algo muito específico. No qual estas mensagens mostradas pelo compilador, e que podem ser vistas na imagem 03, não nos diz o que está de fato errado.

No entanto, existe um conceito, que foi explicado lá nos primeiros artigos, onde falei sobre variáveis e constantes. E está conceito, neste exato momento é muito importante, isto para que possamos compreender como lidar com este tipo de situação. Você pode notar que na linha sete do código 03, estamos declarando uma variável. Certo? Só que lhe pergunto: Que tipo de variável estamos declarando ali, na linha sete? Bem, este é o problema. Se eu não sei, e nenhum outro programador também não sabe. Como o compilador irá saber?

Ok, você pode me dizer: Bem, na linha 25 estou dizendo que quero um tipo que possua oito bytes de largura, e na linha 35 um que possua dois bytes de largura. Certo. Mas isto não diz ao compilador que tipo de variável deverá ser utilizada ali. Note que na linha 25 e na linha 35 NÃO ESTAMOS DECLARANDO A VARIÁVEL. Estamos sim atribuindo um valor a ela. A declaração está sendo feita é nas linhas 23 e 33. Percebe agora o problema?

Bem, mas existe um outro problema ainda maior, que é justamente o que causa a geração de todas aquelas mensagens de erro que podem ser vistas na imagem 03. Que é o fato de que apesar da declaração está sendo feita nas linhas 23 e 33. Estamos declarando algo que é desconhecido do compilador. Que é justamente o tipo de variável da linha sete. Note que as linhas 23 e 33 de fato fazem alguma referência ao tipo que está sendo declarado na linha cinco. Ou seja, uma união.

No entanto, esta união é um template. Sendo assim o tipo de dado a ser utilizado será definido pelo compilador. Como ele não sabe que tipo de dado deverá ser utilizado, todas aquelas mensagens de erro são disparadas. Mas agora vem o conceito a ser adotado. E se você, de fato compreendeu como o template é utilizado em funções e procedimentos, sabe que a declaração da variável é feita em algum ponto. E quando a função, ou procedimento, é sobrecarregado pelo compilador, o tipo de dado já será conhecido. Permitindo assim que o compilador consiga criar a rotina adequada.

E então, sabendo disto, você pode pensar: Como posso dizer ao compilador que tipo de dado deverá ser utilizado? Já que normalmente utilizamos o tipo de dado, seguido de um rótulo que nos diz o nome da variável. E já que estamos fazendo isto nas linhas 23 e 33 no código 03. Não faço ideia de como resolver este problema. Ok, se você chegou neste ponto, e conseguiu de fato entender todos estes conceitos. Chegou a hora de ver como solucionar este problema. A fim de conseguir utilizar template a fim de conseguir sobrecarregar tipos diferentes. Para isto, o MQL5, faz uso de algo que existe na linguagem C e C++. Que é uma declaração especial de variáveis. Podendo ser estas locais ou globais. Mas lembre-se de procurar entender o conceito, e não simplesmente decorar como fazer as coisas. A solução pode ser vista logo abaixo.

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

Note que é algo muito, mais muito sutil que estamos fazendo aqui. Porém ao tentar compilar este código 04, veja o que acontece.

Imagem 04

Ou seja, algo aparentemente insignificante e que não faz muito sentido neste momento, permite que o código venha de fato a ser compilado. E o resultado da execução deste código 04, é o que podemos ver na imagem que vem logo na sequência.

Imagem 05

Que coisa mais linda, e maravilhosa não é mesmo meu caro leitor? Mas o que aconteceu de fato aqui? Por que este código 04 funciona e o código 03 não? E por que estamos fazendo esta declaração estranha nas linhas 23 e 33? Agora estou igual um bote inflável na água. ‘Tô boiando’. Já que não estou entendendo absolutamente nada do que está sendo feito aqui.

Ok, meu caro leitor, então vamos entender o que aconteceu aqui. E por que da declaração precisar ser feita da forma como está sendo feita nas linhas 23 e 33. Muito bem, este é o quarto artigo sobre templates e typename, que estou disponibilizando. No segundo artigo, vimos que poderíamos forçar o compilador a utilizar um ou outro tipo de dado, a fim de permitir que o tipo fosse adequado ao parâmetro que uma função ou procedimento estaria recebendo. Só que naquele caso, a conversão de tipo era durante a atribuição do valor. Para isto, usávamos uma conversão explicita de tipo, colocando o tipo de destino entre parênteses. Algo muito comum em diversos momentos. Como você já deve ter notado em outros códigos, vistos até o devido momento. No entanto, uma coisa é atribuir um valor a uma variável já declarada. Outra coisa, é atribuir um tipo a uma variável ainda não declarada.

Como templates visam justamente criar um modelo de função, procedimento, ou tipo de dado, que podemos utilizar futuramente. Este estará sendo acompanhando da declaração typename. E é aqui onde mora a questão. Note que nos artigos anteriores, demonstrei que o T que acompanha a declaração typename, é na verdade um rótulo, ou nomenclatura para podemos definir algo posteriormente. Sendo assim, quando este T é substituído pelo compilador, teremos a definição do tipo que precisamos utilizar. Sendo assim possível o compilador criar o código de maneira adequada. Então quando na declaração das linhas 23 e 33, declaramos um tipo, da forma que estamos fazendo. Estamos na verdade dizendo ao compilador qual o tipo de dado a ser utilizado no lugar daquele T que é visto acompanhando a declaração typename.

Como ainda não mostrei, como utilizar de forma mais profunda está mesma declaração. Pode ser que você não consiga entender isto, logo de início. Porém, com você pode observar. Funciona. E é por isto que a declaração precisa ser feita desta maneira.

Bem se você achou isto divertido. Então vai adorar o que vem logo em seguida. Isto pelo simples motivo, de que agora iremos remover a macro deste código 04. Tornando ela uma função ou procedimento. E isto de maneira a manter o código 04 funcionando com o resultado que é visto na imagem 05. Mas como isto envolve aplicar o que foi explicado aqui de uma forma ainda um pouco mais avançada. Vamos separar as coisas em um novo tópico.


Por que complicar, se podemos simplificar

Muitos de vocês agora irão pensar que o que estamos fazendo aqui é a mais pura loucura. E que este tipo de material é muito avançado e que não precisamos de fato aprender como fazer este tipo de coisa. De fato, devo concorda com esta opinião. Isto porque, sabendo apenas como criar funções, procedimentos. Declarar variáveis e sabendo como utilizar alguns comandos básicos. Podemos criar praticamente quase qualquer coisa. Porém, toda via e, entretanto, quanto mais ferramentas e recursos, temos a nossa disposição, mais fácil é de fato implementar e construir as coisas. Você pode até construir todo um deposito de armazenamento de grãos utilizando para isto um único prego. E isto de fato já foi feito. Mas seria muito mais simples se pudesse utilizar mais recursos, que no caso seria pregos.

Pois bem, o que iremos fazer aqui e agora, é criar algo, que no caso será uma função, que substitua a macro que está sendo definida na linha 14 do código 04. Isto sim será algo divertido e interessante de ser feito. Mas antes de fazermos isto, devo lembrar a você, meu caro e estimado leitor. Que o objetivo aqui é a didática. E que você antes de tentar entender o que será feito aqui, deve primeiro entender o que foi feito no tópico anterior. Somente assim as coisas e fato irão fazer sentido.

Ok. Então vamos começar com a ideia inicial. Ou seja, retirar a macro do código 04 e tentar tornar ela uma função. Mas antes realmente fazer isto, vamos começar montando um procedimento, que ao meu ver é mais simples de entender. Então, você todo feliz, por ter aprendido algo novo, vai e criar o seguinte código visto logo abaixo.

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

E ao tentar compilar este código 05, para a sua surpresa e decepção, eis que lhe é mostrado o seguinte resultado visto logo abaixo.

Imagem 06

Cara, de novo este tipo de coisa acontecendo? Que coisa mais chata e sem graça. Calma meu caro leitor, calma. Não existe motivo para pânico ou desespero neste momento. Ainda não. O problema desta vez é muito parecido com o que estava acontecendo no tópico anterior. Mas a solução é diferente neste caso. Preste atenção, ao seguinte fato. Nas linhas 19 e 29, estamos fazendo uma chamada ao procedimento da linha 34. Até aí tudo certo. O problema é que o argumento esperado pelo procedimento é justamente o tipo, que está sendo definido na linha cinco. E como este tipo é um template, o compilador não sabe como lidar com isto. Já que não temos como dizer ao compilador qual tipo de dado a ser utilizado.

Mas espere um pouco aí. Como assim? Estamos dizendo o tipo de dado nas linhas 14 e 24. Sim meu caro leitor. Porém, nestas linhas 14 e 24, você está definindo localmente o tipo de dado a ser utilizado. No entanto, isto não é repassado para o procedimento da linha 34. Justamente por ser um tipo complexo. E não um tipo primário, como acontece quando estamos repassando as coisas como era feito antes. Além disto, temos um outro pequeno detalhe aqui. Como o tipo de dados a ser utilizado permite a sobrecarga de tipo. Ao tentarmos repassar isto para dentro de uma função ou procedimento, precisamos nos assegurar que a função ou procedimento TAMBÉM PDOERÁ SER SOBRECARREGADA. Por isto que eu falei que este tipo de coisa, é extremamente divertida.

Ou seja, pelo fato de que o argumento pode sofrer sobrecarga, visto que isto está sendo feito dentro do procedimento OnStart. Precisamos que a função ou procedimento, que receba este mesmo tipo de dado, também possa ser sobrecarregada. Assim tudo ficará adequado e o compilador conseguirá compreender o código a fim de criar o executável que desejamos.

Então, tendo entendido isto e sabendo já como criar uma função sobrecarregada que utiliza template para isto. Você pega e modifica o código 05, a ponto de criar o código 06. Que podemos visualizar logo abaixo na sequência.

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

Legal. Agora sim temos um código adequado. O compilador finalmente irá conseguir entender o que estou tentando fazer. Então mais uma vez, todo feliz e satisfeito, você pede para o compilador tentar criar o executável. Já que a sobrecarga utilizando template de fato foi implementada como você pode observar neste código 06. Porém, de maneira extremamente decepcionante, eis que o compilador lhe informa o que é visto logo abaixo.

Imagem 07

Agora sim, definitivamente este sistema está de sacanagem comigo. Só pode. Não consigo entender. Por que razão DEUS tem sido tão cruel comigo. Me fazendo tentar criar algo que eu poderia fazer de outra maneira. Calma meu caro leitor. Tenha calma. Isto que estamos fazendo, é de fato algo que faz muitos criarem códigos sempre da mesma maneira. Tornando assim, coisas que poderiam ser feitas de maneiras muito mais simples com menos possibilidades de erro, em algo monstruoso e com repetição de vários pontos que poderiam ser melhor implementados.

De fato, criar um template, não é uma das tarefas mais simples. Devido a isto, é que você dificilmente irá ver códigos fazendo uso deste tipo de recurso. Porém, entender isto irá lhe ajudar em muitos momentos. Já que torna o código consideravelmente mais simples, pelo ponto de visto de programação. Já que joga toda a complexidade para o compilador poder lidar com ela. Agora vamos entender por que de este erro está correndo. Este tipo de coisa realmente no começo, faz a gente sofrer bastante. Penei durante muito tempo, até aprender a fazer isto de maneira correta. Visto que este tipo de coisa, é muito utilizada em C e C++. Como MQL5, se baseia em muitos conceitos em coisas vista em C e C++. Uma vez que aprendi a trabalhar com C e C++, aprender MQL5, foi moleza. Quase como brincadeira de criança. Mas aprender isto, que estou explicando aqui, de fato foi bem complicado.

Preste atenção. Apesar do compilador está reportando que o erro se encontra nas linhas 19 e 29. Ele está nos direcionando para o local errado. Não por culpa do compilador. Mas por nossa própria culpa. E já vou explicar o motivo. No tópico anterior, você lembra de que foi preciso tomar medidas para que o compilador pudesse entender que tipo de dados estávamos utilizando? Onde fizemos uma declaração que pode ser vista sendo replicada neste código 06, nas linhas 14 e 24.

Pois bem, apesar de isto está sendo feito de maneira correta no procedimento OnStart. O mesmo não está sendo feito no procedimento Swap. Você pode até pensar que a declaração do template do procedimento Swap está correta. Porém ela não está devidamente correta. Isto foi explicado nos três artigos anteriores. Porém, é difícil notar onde está o erro aqui. Mas, se você não está conseguindo ver onde o erro se encontra. É por que, ainda não conseguiu de fato entender como o template de uma função ou procedimento, consegue sobrecarregar a mesma.

Observe que na declaração do template na linha 34, estamos dizendo que iremos usar o tipo T para criar a sobrecarga. Correto? Pois bem, agora olhe novamente a linha 35. Qual o argumento, que no caso é somente um, que está recebendo este tipo T? Não existe nenhum argumento recebendo este tipo T. Ou seja, apesar da declaração informar que estamos criando um template, ele na verdade não está de fato sendo criado. Agora, volte nos artigos anteriores e veja como a declaração estava sendo feita. Você irá ver algo parecido com o que é visto logo abaixo.

                   .
                   .
                   .
15. template <typename T>
16. T Averange(const T &arg[])
                   .
                   .
                   .

Fragmento 01

Veja neste fragmento 01, o seguinte fato: O tipo T está sendo declarado na linha 15, mas logo depois o estamos usando na linha 16. Isto a fim de que o argumento possa utilizar o tipo quando o compilador for criar a função ou procedimento. Sem que isto fosse feito, o compilador, não saberia como lidar com a situação. E a mesma coisa está acontecendo aqui no código 06. Temos a declaração, mas não estamos usando-a. Ou melhor, não estamos dizendo ao compilador como usar a declaração. Para fazer isto, precisamos modificar novamente o código 06, para que o compilador consiga entender o que estamos tentando fazer. Assim o código que irá de fato funcionar é visto logo abaixo.

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

Definitivamente agora sim, temos um código que de fato funciona e que produz um resultado que é o mesmo que podemos ver na imagem 05. Mas lhe pergunto: Você de fato conseguiu entender por que este código 07 funciona meu caro leitor? Você fosse pedido, em um teste para trabalhar como programador, que você criasse um código, que gerasse um resultado igual a este que o código 07 gera. Fazendo uso de uma função ou procedimento.

Porém sem utilizar um template para isto. E precisando utilizar o template da união declarada na linha quatro do código 07. Você conseguiria fazer isto? Conseguiria cumprir esta tarefa a fim de conseguir a vaga de programador, ou simplesmente diria que tão coisa não seria possível de ser feita? Já que em um dado momento, mencionei que o procedimento ou função neste caso, precisaria utilizar uma template para que pudesse ser gerado.

Existem conceitos e detalhes que tornam as coisas bem interessantes. Mas de fato podemos criar o código 07 de uma outra forma. A fim de que controlemos a sobrecarga diretamente, sem de fato utilizar um template para isto. Como este tipo de coisa exige uma explicação adequada para ser feita e devidamente compreendida, iremos ver isto no próximo artigo.


Considerações finais

Neste artigo, vimos como podemos controlar e criar, templates de uma forma bem mais geral e controlada. Como sei que este conteúdo é muito complicado de ser compreendido assim, de uma hora para outra. Pois passei por isto, quando estava estudando C e C++ lá no começo da minha carreira como programador. Peço a você, meu caro leitor, que tenha paciência. Que procure praticar o que está sendo mostrado nestes artigos. E principalmente, procure entender os conceitos que estão sendo mostrados. Pois se você entender os conceitos, conseguirá criar praticamente qualquer tipo de código. Com bem menos dificuldades que aqueles que insistem em decorar trechos de código e sintaxe de linguagens de programação. Então pratique e estude com calma o conteúdo presente no anexo. E nós vemos no próximo artigo.

Arquivos anexados |
Anexo.zip (3.9 KB)
Colmeia artificial de abelhas (ABHA): Testes e resultados Colmeia artificial de abelhas (ABHA): Testes e resultados
Neste artigo, continuaremos o estudo do algoritmo de colmeia de abelhas ABHA, aprofundando-nos na escrita de código e analisando os métodos restantes. Lembremos que cada abelha no modelo é apresentada como um agente individual, cujo comportamento depende de informações internas e externas, bem como de seu estado motivacional. Realizaremos testes do algoritmo em diferentes funções e apresentaremos os resultados em uma tabela de classificação.
Simulação de mercado (Parte 05): Iniciando a classe C_Orders (II) Simulação de mercado (Parte 05): Iniciando a classe C_Orders (II)
Neste artigo, explicarei como o Chart Trade conseguirá lidar, junto com o Expert Advisor, a um pedido do usuário para encerrar todas as posições que se encontram em aberto. Parece ser algo simples. Porém existem alguns agravantes que você precisa saber como lidar com eles.
Redes neurais em trading: Injeção de informação global em canais independentes (InjectTST) Redes neurais em trading: Injeção de informação global em canais independentes (InjectTST)
A maioria dos métodos modernos de previsão de séries temporais multimodais utiliza a abordagem de canais independentes, ignorando a dependência natural entre os diferentes canais de uma série temporal. Para melhorar a eficiência dos modelos, é fundamental utilizar equilibradamente duas abordagens: canais independentes e mistos.
Colmeia artificial de abelhas — Artificial Bee Hive Algorithm (ABHA): Teoria e métodos Colmeia artificial de abelhas — Artificial Bee Hive Algorithm (ABHA): Teoria e métodos
Neste artigo, exploramos o algoritmo Artificial Bee Hive Algorithm (ABHA), desenvolvido em 2009. Voltado para a solução de problemas de otimização contínua, o algoritmo é utilizado para encontrar o melhor caminho entre dois pontos. Analisaremos como o ABHA se inspira no comportamento das colônias de abelhas, no qual cada abelha desempenha um papel único que contribui para uma busca mais eficiente por recursos.