English Русский Español Deutsch Português
preview
初級から中級まで:テンプレートとtypename(IV)

初級から中級まで:テンプレートとtypename(IV)

MetaTrader 5 |
19 0
CODE X
CODE X

はじめに

なお、本記事の内容は教育目的に限定されており、完成されたアプリケーションとして捉えるべきではありません。ここでの目的は、提示された概念そのものを応用することではありません。

前回の「初級から中級まで:テンプレートとtypename(III)」では、多くの初心者が特に難しいと感じるテーマを取り上げました。これは、MQL5プログラマにとって非常に重要な「テンプレート」という概念を、多くの方がまだ十分に理解していないためです。読者の多くがプログラミング経験の浅い方であることを踏まえ、できるだけわかりやすく解説するよう努めています。

そのため、前回の記事はやや唐突な形で終わりました。エラーの図と、実行ファイルを生成できなかったコードで締めくくられていたため、動作しないコードに失望された方もいらっしゃったかもしれません。しかし、私は皆さんにとって非常に難しく感じられる「型のオーバーロード」というテーマを紹介し始めたところでした。実際には、私たちが今作ろうとしているのは型のオーバーロードそのものではなく、状況に応じてコンパイラが自動的に適切な型を生成できるテンプレート型です。

原則として、記事内で提示するコードはすべて動作するものです。しかし今回は説明を簡潔にするため、あえて一部制限を設けています。つまり、実際にコードを実装しても常に正しく動作するとは限らない、ということを理解していただきたいのです。コードの問題を自力で解決したいと考える方は多いですが、正しい考え方や使用している言語の特性を理解していなければ、問題解決は困難です。その理解がないと、プロのプログラマにとっては些細なことでも、初心者には大きな壁となってしまいます。

これまでに、テンプレートを使って関数や手続きをオーバーロードする方法を見てきました。しかし、これを他のタイプのアプリケーションに応用しようとすると、前回の記事の最後で示したように、少し複雑になります。そこで、ここから新しいトピックに進みましょう。


テンプレート型の使用

実際のところ、適用する概念自体は単純です。しかし、それを正しく実装できるように具体的にイメージするのは、なかなか難しいものです。これまでの記事で扱った内容をもとに、再び考えていきましょう。ただしその前に、正しく動作し、コンパイル可能なソースコードを確認しておきます。以下にそのコードを示します。

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. //+------------------------------------------------------------------+

コード01

コード01をMetaTrader 5でコンパイルして実行すると、次の結果が得られます。

図01

この結果は明らかに正しくありません。これは図01で強調表示されている部分に関係しています。しかし、特定の使用目的によっては、この結果が正しい場合もあります。けれども、私たちが求めているのはそうではありません。私たちは、33行目で宣言されている値が2バイト幅になることを望んでいます。ところが、共用体(union)の幅が8バイトであるために、このような宣言方法を使わざるを得ず、その結果、図01で確認できるように、最終的な結果がまったく不適切なものになってしまっています。

もっとも、前回の記事でもテンプレート型自体はすでに作成していました。そこで示したコードに誤りはありませんでしたが、何かがまだ不足していました。なぜ今回のように、すべてをこのような手順で実行しなければならないのかを説明するのは少し難しいところです。そこで、この記事を一度ここで締めくくり、皆さんが落ち着いてこの問題について考察し、探求できるようにしたいと思います。目的は、なぜコードが正しく動作しないのかを理解することです。そしてここで、正しく動作させるための方法と、なぜそのような特定の実装方法を取らなければならないのかを解明していきます。そうすることで、コンパイラがどのように処理すべきかを正しく理解できるようになります。

次の自然なステップは、コード01を少し異なるものに置き換えることです。これは、前回の記事で示した結果を得る前の段階でおこなう作業です。その結果、次のようなコードになります。

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. //+------------------------------------------------------------------+

コード02

さて、コード02を実行すると、最終結果は下の図のようになります。

図02

図02では、目指していた結果を正確に得ることができました。つまり、8バイトを必要とする型の値が表示され、さらに、期待どおり2バイトを必要とする別の値も表示されています。しかし、その実現方法に注目してください。たとえ動作していたとしても、私たちはコードを作成し、それに適切な変更を加えることで自分たちに過剰な負担をかけています。このようにしてコードが大きく、複雑になるにつれて、新しい要素を追加し続けなければならないため、エラーが発生する可能性も高くなってしまいます。

ご覧のとおり、私たちは非常に単純なことをおこなっているにすぎませんが、コードが少しずつ分かりにくくなり始めています。ここで、型テンプレートを使用するという発想が生まれます。その理由は、15行目から29行目のコードブロックと、31行目から45行目のコードブロックとの違いが、union内で定義されている型だけにあるからです。この違いは、コード02の18行目と34行目に見られます。

したがって、私たちはテンプレートの作成を始めます。その主な目的は、unionがオーバーロードされている同じコード02を簡素化することにあります。このように、提示された概念を応用して、関数または手続きのテンプレートを作成します。この場合、それをオーバーロードすることも可能です。その結果、次のようなコードが得られます。

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. //+------------------------------------------------------------------+

コード03

ここから混乱が始まります。そして、その原因は次の点にあります。コード03は、コード02で示されたものを作成しようとしていますが、その方法として、コード01で示されたものと非常によく似た仕組みを使用しています。しかし、コード03をコンパイルすると、コンパイラは次のエラーメッセージを出力します。

図03

ここで、明らかに何かがおかしいということが分かります。しかし、一見したところではその理由がまったく理解できません。なぜなら、ぱっと見た限りでは、テンプレートの宣言は正しくおこなわれているように見えるからです。では、コード03のどこに問題があり、なぜコンパイルができないのでしょうか。コンパイルができないということは、コンパイラが何をすべきか理解できていないということです。

多くの方は、コンパイラが報告する問題を解決しようと常に努力していると思います。それは非常に重要なことです。しかし、今回の場合、図03に表示されているコンパイラのエラーメッセージは、実際にどこに問題があるのかを示してはいません。

ここで、最初の方の記事で説明した、変数と定数に関する概念を思い出してください。今こそ、その概念が非常に重要になります。これを理解することで、このような状況でどのように対処すべきかが見えてきます。コード03の7行目を見てください。そこでは変数を宣言していますね。ではお尋ねします。7行目で宣言されているのはどんな変数でしょうか。。まさに、そこが問題の核心です。私自身も他のプログラマもその変数の正体を知らないのであれば、コンパイラがそれを理解できるはずがありません。

あなたはこう答えるかもしれません。「25行目では8バイト幅の型を、35行目では2バイト幅の型を指定していますよね」と。その通りです。しかし、それだけではコンパイラに、どの型の変数を使うのかを伝えることはできません。25行目と35行目では、変数の宣言おこなっていません。そこでは値を代入しているだけです。実際の宣言は、23行目と33行目にあります。問題が見えてきましたか。

さらに、もう一つより深刻な問題があります。それが、図03に示されている多くのエラーメッセージの原因です。実際のところ、23行目と33行目には宣言が存在しますが、そこではコンパイラにとって未知の型が宣言されています。つまり、7行目の変数の型です。23行目と33行目は、5行目で宣言された型、つまりunionを参照しています。

しかし、このunionはテンプレートとして定義されています。したがって、使用されるデータ型はコンパイラによって決定されます。つまり、コンパイラはどのデータ型を使うべきかを知らないために、これらのエラーメッセージが発生しているのです。ここで受け入れるべき重要な概念があります。テンプレートが関数や手続きでどのように使われるかを理解している方なら、ある時点で変数が宣言されることをご存じでしょう。関数や手続きがオーバーロードされる場合、コンパイラはすでにデータ型を認識しているため、適切な手続きを生成することができます。

このことを踏まえると、どのようにしてコンパイラに使用するデータ型を伝えればよいのかという疑問が生まれます。通常は、「データ型+変数名」という形で指定します。そして、コード03の23行目と33行目では、まさにそのようにしています。にもかかわらず、この問題は解決されません。つまり、現時点ではその方法ではコンパイラに正しく伝わらないのです。もし、ここまで読んでこれらの概念を理解できたなら、次はこの問題を解決し、テンプレートを使って異なる型をオーバーロードする方法を見ていく段階です。この目的のために、MQL5では特別な変数宣言の方法が用意されています。これにより、変数をローカルまたはグローバルとして宣言できます。この機能は、すでにC言語およびC++言語にも存在しているものです。ただし、忘れないでください。最も重要なのはやり方を覚えることではなく、その概念を理解することです。解決策はすぐ下にあります。

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. //+------------------------------------------------------------------+

コード04

今、私たちが何をしているのかをよく見てください。これは非常に微妙な変更です。しかし、コード04をコンパイルしようとすると、次のような結果が表示されます。

図04

つまり、いまのところあまり意味がなさそうな、取るに足らない変更のように見えるものが、実際にはコードをコンパイル可能にしているのです。そして、コード04を実行した結果は次の図に示されています。

図05

なんと美しく、そして驚くべき結果でしょう。しかし、ここで疑問が生じます。なぜコード04は動作し、コード03は動作しないのでしょうか。そして、なぜ23行目と33行目であのような奇妙な宣言をおこなっているのでしょうか。「もう完全に混乱しました。呆然としています。。何が起きているのかまったく理解できません。」

では、ここで何が起こっているのか、そしてなぜ23行目と33行目のような形式で宣言をおこなわなければならないのかを見ていきましょう。今回の記事は、テンプレートとtypenameに関する第4回目です。第2回の記事では、関数や手続きが受け取るパラメータの型に合わせて、コンパイラに特定のデータ型を使うよう強制する方法について説明しました。この場合、値の代入時に型変換がおこなわれます。そのために、対象となる型をかっこで囲む明示的な型変換を使用しました。このような処理は、他のコードでも頻繁に登場することに気づいたかもしれません。しかし、ここで重要なのは、すでに宣言されている変数に値を代入することとまだ宣言されていない変数に型を割り当てることとは全く別の話であるという点です。

テンプレートの目的は、将来使用できるように、関数、手続き、データ型などのモデルを作成することにあります。したがって、テンプレートには常にtypename宣言が伴います。ここが今回の重要なポイントです。これまでの記事で説明したように、typename宣言に付随している文字「T」は、後で何かを定義するためのラベルのようなものです。したがって、コンパイラがこのTを置き換えると、使用すべきデータ型の定義が得られます。こうして、コンパイラは正しいコードを生成できるようになるのです。つまり、23行目および33行目でおこなっているような形式の宣言によって、私たちはコンパイラに対し、typename宣言で使われているTの代わりに、どのデータ型を使用すべきかを明示的に伝えているのです。

この宣言方法の使い方については、まだ詳しく説明していないので、理解するのに少し時間がかかるかもしれません。しかし、見てのとおり、これは正しく動作します。したがって、宣言はこのような形式で設計する必要があるのです。

もし今回の内容を興味深いと感じたなら、次にご紹介する内容もきっと気に入っていただけると思います。というのも、次はコード04で使用したマクロを関数または手続きに変換するからです。目的は、コード04が引き続き動作し、図05に示された結果を得られるようにすることです。ただし、これは今回学んだ内容をさらに高度に応用するものになるため、次回の記事で別途詳しく扱うことにします。


簡単にできることをわざわざ複雑にするのか

多くの方は、私たちがここでおこなっていることは論理的ではないとか、この内容は高度すぎて学ぶ必要がないと感じるかもしれません。実のところ、私もその意見にはある程度賛成です。関数や手続きを作成し、変数を宣言し、いくつかの基本的な構文を使う方法さえ知っていれば、ほとんどのものは作成できます。そして、私たちが利用できるツールやリソースが多ければ多いほど、実装や開発は容易になります。1本の釘だけでも穀物貯蔵用エレベーターを建設することは可能です。実際、それはすでにおこなわれています。しかし、もし複数の「釘」を使えるのであれば、それはずっと簡単になるでしょう。

さて、ここではコード04の14行目で定義されているマクロを置き換える関数を作成します。これはとても興味深い作業になるでしょう。ただし、その前に皆さんにもう一度思い出していただきたいことがあります。私たちの目的は学ぶことです。ここでおこなっていることを理解する前に、まずは前のトピックで何がおこなわれたのかをしっかり理解する必要があります。そうして初めて、すべてが意味を持つようになります。

それでは、最初のアイデアから始めましょう。つまり、コード04からマクロを取り除き、それを関数に変換してみるということです。しかしその前に、より理解しやすいと思われる手続きを作成してみましょう。そうすることで、次に示すようなコードを作成できるようになります。

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. //+------------------------------------------------------------------+

コード05

コード05をコンパイルしようとすると、驚きと落胆の入り混じった結果が画面に表示されます。その内容が次に示されています。

図06

「また?もう笑えませんね。」落ち着いてください。慌てたり、絶望したりする必要はありません。今のところは、です。問題は、前回のトピックで取り上げた内容と非常によく似ています。ただし、今回は解決方法が少し異なります。まず、次の点に注目してください。19行目と29行目で定義された手続きが、34行目で呼び出されています。ここまでは順調です。しかし問題は、その手続きが5行目で定義されているデータ型を正確に受け取ることを期待している点にあります。そして、この型がテンプレートとして定義されているため、コンパイラは、どのデータ型を使用すべきかを判断できないのです。私たちは、その情報をコンパイラに伝える方法を持っていません。

「でも待ってください。14行目と24行目でデータ型を宣言していますよね?」はい、その通りです。しかし、14行目と24行目ではローカルに使用するデータ型を定義しているだけです。この型情報は、34行目で呼び出される手続きには渡されません。その理由は、この型が複合型であり、以前のようにそのまま値が渡されるような基本型ではないためです。もうひとつ小さな注意点があります。データ型が型のオーバーロードを許可している場合、それを関数や手続きの内部に渡すときには、受け取る関数または手続きもオーバーロード可能である必要があるということです。まさに、こうした点が今回のテーマを面白くしている要素なのです。

つまり、OnStart手続き内の引数がオーバーロード可能である以上、同じデータ型を受け取る関数や手続きもまたオーバーロード可能でなければなりません。そうすることで、すべてが適切に整合し、コンパイラはコードを正しく理解して、目的の実行可能ファイルを生成できるようになります。

この概念を理解し、テンプレートを使用するオーバーロードされた関数を作成する方法を習得したら、コード05を修正してコード06を作成してみましょう。

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. //+------------------------------------------------------------------+

コード06

素晴らしいですね。これで正しいコードが完成しました。コンパイラもようやく、私たちが何をしようとしているのかを理解できるようになります。このため、テンプレートを使用したオーバーロードがコード06で実装されていることが確認できたので、コンパイラに実行可能ファイルの作成を依頼してみましょう。ところが残念なことに、コンパイラは次のような結果を出力します。

図07

「もう完全にこのシステムにいじめられている気がします。もはや他に言葉がありません。正直、なぜこんなことが起きたのか理解できません。」でも心配しないでください、読者の皆さん。私たちは今、まさに多くの人がつまずくところを体験しているのです。同じようなコードを繰り返し書く原因となる“ある問題”を解消しようとしており、本来ならもっと簡単に、そしてエラーの少ない形で書けるものを、複雑にしてしまう落とし穴を明らかにしています。

実際のところ、テンプレートの作成は決して簡単な作業ではありません。そのため、このようなリソースを活用したコードを目にする機会はあまり多くありません。しかし、この仕組みを理解することは非常に重要です。なぜなら、テンプレートはコードを大幅に簡潔にし、複雑さをコンパイラに任せることができるからです。それでは、なぜこのエラーが発生したのかを解明していきましょう。最初のうちは、このような問題に多くの人が苦しみます。私自身も、これを正しくおこなう方法を学ぶのにかなり時間がかかりました。というのも、同様の要素はC言語やC++言語でも頻繁に使用されるからです。MQL5はCおよびC++の多くの概念を基礎としているため、これらの言語を理解してからは、MQL5を学ぶのはとても簡単でした。まるで子どもの遊びのように感じたほどです。ですが、今ここで説明している内容を習得するのは決して容易ではありませんでした。

ここで注意してください。コンパイラはエラーが19行目および29行目にあると報告していますが、実際にはそれは誤った場所を指しています。もちろん、これはコンパイラのせいではありません。私たちの側の問題です。その理由を説明しましょう。前回のトピックを思い出してください。コンパイラに対して、どのデータを使用しているのか」を理解させるために、ある手順を踏む必要がありましたね。その際に作成した宣言は、コード06の14行目と24行目に示されています。

OnStart手続きではこれが正しく機能していました。しかし、Swap手続きではそうなっていません。一見すると、Swap手続きのテンプレート宣言も正しく見えるかもしれませんが、実はそうではありません。この点については、これまでの3つの記事でもすでに触れましたが、ここではそのエラーが非常に見えづらい形で現れています。もしどこにエラーがあるのか分からないとしたら、それはまだ「関数または手続きのテンプレートが、どのようにオーバーロードを扱うのか」を十分に理解していないということです。

34行目のテンプレート宣言を見てください。ここで私たちはT型を使ってオーバーロードをおこなうことを示していますね。では、次に35行目をもう一度見てみましょう。その中で、どの引数(この場合、唯一の引数)がT型を受け取っているでしょうか。答えは、「存在しません」。つまり、宣言上ではテンプレートを作成しているように見えても、実際にはそれがまったく使用されていないのです。では、以前の記事に戻って、どのように宣言をおこなっていたかを確認してみましょう。次のような記述を見たはずです。

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

スニペット01

スニペット01では、次の点に注目してください。15行目でT型が宣言されていますが、その直後の16行目でそれを使用しています。これは、コンパイラが関数や手続きを生成するときに、引数がその型を使用できるようにするために必要な処理です。もしこの手順を踏まなければ、コンパイラはどのように処理を進めればよいのかを判断できません。同じことがコード06にも当てはまります。私たちはテンプレートの宣言をおこなっていますが、実際にはそれを使用していないのです。つまり、コンパイラに対して、この宣言をどのように使うべきかを伝えていないということです。この問題を解決するには、コンパイラが私たちの意図を理解できるように、コード06をもう一度修正する必要があります。以下に示すのが、正しく動作するコードです。

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. //+------------------------------------------------------------------+

コード07

もちろん、これで私たちは図05と同じ結果を出す動作するコードを手に入れました。ですが、ここで皆さんに質問したいと思います。なぜコード07が動作するのか、その理由を理解できましたか。想像してみてください。もしあなたがプログラマとして就職するための試験で、「コード07と同じ結果を、関数または手続きを使って再現しなさい」と出題されたとします。

ただし、テンプレートは使用せず、かつコード07の4行目で宣言されたunionテンプレートは必ず使う、という条件です。あなたはそれを実現できるでしょうか。それとも、そんなことは不可能だと答えるでしょうか。以前どこかで述べたように、このようなケースでは手続きまたは関数をテンプレートとして生成する必要があります。

こうした概念や仕組みには、非常に興味深い部分が多くあります。たとえば、コード07をテンプレートを使わずに、オーバーロードを直接管理する別の方法で作成することも可能です。ただし、このような仕組みを正しく理解するためには、丁寧な説明が必要になります。そのため、このテーマについては次回の記事で詳しく扱うことにしましょう。


最終的な考察

今回の記事では、テンプレートをどのように扱い、より一般的かつ正確に作成するかについて解説しました。この内容は一度で完全に理解するのが難しいものです。私自身もプログラミングを始めたばかりの頃、CやC++を学んでいたときに同じような経験をしました。ですから、どうか焦らず、忍耐強く学習を続けてください。この記事で紹介した内容を何度も練習し、そして何よりも提示された概念を理解することを重視してください。その概念をしっかりと理解すれば、ほとんどのコードは自分で書けるようになります。それも、文法や構文を丸暗記しているだけの人よりも、ずっと少ない労力で。したがって、ぜひ落ち着いて、今回の内容を自分の手で試しながら学んでください。次の記事でまたお会いしましょう。

MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/15670

添付されたファイル |
Anexo.zip (3.9 KB)
事後取引分析:ストラテジーテスターにおけるトレーリングストップと新しいストップレベルの選択 事後取引分析:ストラテジーテスターにおけるトレーリングストップと新しいストップレベルの選択
取引の質をさらに高めるため、今回はストラテジーテスターで完了済みの取引を分析するテーマを引き続き取り上げます。異なる種類のトレーリングストップを使用すると、既存の取引結果がどのように変化するかを見ていきましょう。
Pythonの価格変動離散化手法 Pythonの価格変動離散化手法
Python + MQL5を使用した価格離散化手法を見ていきます。本記事では、バー生成に関する幅広い手法を実装したPythonライブラリの開発経験についご紹介します。クラシックなボリュームバーやレンジバーから、よりエキゾチックな練行足やカギ足といった手法までを網羅します。スリーラインブレイクローソク足やレンジバーの統計分析をおこないながら、価格を離散的に表現する新たな方法を探っていきます。
強化学習と弱者淘汰を組み合わせた進化型取引アルゴリズム(ETARE) 強化学習と弱者淘汰を組み合わせた進化型取引アルゴリズム(ETARE)
この記事では、進化アルゴリズムと深層強化学習を組み合わせた、外国為替取引のための革新的な取引アルゴリズムを紹介します。このアルゴリズムは、非効率な個体を絶滅させるメカニズムを使用して取引戦略を最適化します。
MetaTrader 5での取引の視覚的な評価と調整 MetaTrader 5での取引の視覚的な評価と調整
ストラテジーテスターは、単に自動売買ロボットのパラメータを最適化するだけでなく、さらに幅広い活用が可能です。本記事では、口座の取引履歴を事後に評価し、ストラテジーテスター上でポジションのストップロスを変更することで取引の調整をおこなう方法を紹介します。