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

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

MetaTrader 5 |
75 2
CODE X
CODE X

はじめに

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

前回の「初級から中級まで:テンプレートとTypename (II)」では、プログラマの日常的な状況での対処方法について説明しました。趣味として一時的にプログラミングをおこなう方も、プロのプログラマも、関数や手続きのテンプレートを使うことが有用になる場面があります。MQL5ではあまり一般的ではなく、常に適用できるわけではありませんが、こうした概念が存在することを知っておくことは有益です。そして、コードを修正する際に混乱しないよう、正しく理解しておく必要があります。

テンプレートは関数や手続きだけに適用されるものではありません。実際には、テンプレートの実用的な応用範囲は広く、開発するアプリケーションの種類によってその有用性は変わります。ここで改めて強調したいのは、テンプレートを使わなくても同じ種類のアプリケーションを実装することは可能であるという点です。しかし、MQL5のこうしたツールやリソースを活用することで、実装段階がより簡単かつ楽しくなり、複雑で厄介な検出エラーを回避するのにも役立ちます。


ローカル変数におけるパターン

これまでの2回の記事では、関数や手続きのテンプレートのみを取り上げ、比較的シンプルで興味深い内容でした。しかし、この説明を通して、関数や手続きのテンプレートは、コンパイラに作業を委譲できる有効な手段であることが明確になりました。特に、関数や手続きのオーバーロードが発生する場合に有効です。ただし、これまで提示した例は非常に単純であり、使用するデータ型は関数や手続きの入力パラメータにのみ関連していました。もちろん、より高度な知識へと発展させることも可能です。

理解を深めるために、まずは単純な例から始めましょう。ここでは平均値の計算を取り上げます。簡単な例ではありますが、テンプレートの応用方法を確認するには良い題材です。なお、ここで提示するコードはあくまで教育目的であり、実務でそのまま使えるものではない点にご注意ください。

関数や手続きのオーバーロードについては既に理解している前提で進めます。そのため、本質的に注目すべき部分に絞り、不要なコードは省略しています。以下に、最初のサンプルコードを示します。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define PrintX(X) Print(#X, " :: ", X)
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     const double d_values[] = {2.5, 3.4, 1.25, 2.48, .85, 3.75};
09.     const long i_values[] = {8, 3, 4, 6, 4, 5, 7};
10.     
11.     PrintX(Averange(d_values));
12.     PrintX(Averange(i_values));
13. }
14. //+------------------------------------------------------------------+
15. double Averange(const double &arg[])
16. {
17.     double local = 0;
18. 
19.     for (uint c = 0; c < arg.Size(); c++)
20.         local += arg[c];
21. 
22.     return local / arg.Size();
23. }
24. //+------------------------------------------------------------------+
25. long Averange(const long &arg[])
26. {
27.     long local = 0;
28. 
29.     for (uint c = 0; c < arg.Size(); c++)
30.         local += arg[c];
31. 
32.     return local / arg.Size();
33. }
34. //+------------------------------------------------------------------+

コード01

目の前にあるコードは、データセットの平均値を計算することを目的としています。平均値の計算方法がわからない方のために簡単に説明すると、すべての値を合計し、その合計を要素の数で割る操作です。この計算自体は単純ですが、ひとつ小さな問題があります。値が正の数と負の数の両方を含む場合、得られる平均値が実際に知りたい情報を正確に反映しない可能性がある点です。しかし、ここでは重要な点ではありません。この話を出したのは、たとえ他人が作成したコードであっても、必ずしも私たちが必要とする情報を提供してくれるわけではないことを理解していただくためです。また、他のプログラマが作成したコードは、そのプログラマが求めていた結果に対応するよう設計されており、私たちが求めている内容とは必ずしも一致しないという点も覚えておいてください。

しかし、コード01の動作を詳しく説明する必要はないと思います。このコードではオーバーロードを使用して、平均値の計算を最適な形で実行しているためです。この処理結果は、以下の図で確認できます。

図01

つまり、内容自体は非常に単純で些細なものです。しかし、特に注目してほしいのは、15行目と25行目に実装されている関数です。これらの関数には共通点があります。それはほぼ同一で、違いはひとつの特定の要素、すなわち使用しているデータ型に関する部分だけです。それ以外の点では見た目に違いはありません。もしこの点に気づいたなら、それは重要な理解が進んでいる証拠です。つまり、コード01にあるこの2つの関数をテンプレートに変換できるということです。これによって作業が大幅に簡単になります。「ただし、ひとつ気になる点があります。両方の関数にはローカル変数があり、それが17行目と27行目にあります。関数呼び出しをテンプレートとして宣言し、実装する方法は、以前の記事で見た通り理解しています。しかし、このローカル変数の扱いが少し悩ましいです。もしかすると、以下のコードのような方法で対応できるかもしれません。」

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define PrintX(X) Print(#X, " :: ", X)
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     const double d_values[] = {2.5, 3.4, 1.25, 2.48, .85, 3.75};
09.     const long i_values[] = {8, 3, 4, 6, 4, 5, 7};
10.     
11.     PrintX(Averange(d_values));
12.     PrintX(Averange(i_values));
13. }
14. //+------------------------------------------------------------------+
15. template <typename T>
16. T Averange(const T &arg[])
17. {
18.     double local = 0;
19. 
20.     for (uint c = 0; c < arg.Size(); c++)
21.         local += arg[c];
22. 
23.     return (T)(local / arg.Size());
24. }
25. //+------------------------------------------------------------------+

コード02

実際、コード02のように実装することは可能です。しかし、このコードをコンパイルしようとすると、コンパイラーから次のような警告が表示されます。

図02

ここで注意していただきたいのは、これはエラーメッセージではなく警告であるという点です。この警告が発生するのは、21行目で配列の値を合計する際に、浮動小数点数と整数値を加算する瞬間が発生するためです。このことにより、最終的な結果がわずかに歪む可能性があります。しかし、コード02を実行すると、結果は図01と同じになります。それでも、このコンパイラの警告は、一部のプログラマにとって気になる場合があります。では、この問題をどのように解決すればよいのでしょうか。問題を解決するには、型変換をおこなう必要があります。これは、23行目でおこなった型変換と同じ考え方で、21行目に適用すればよいのです。ただし、以下の方法は使用してはいけません。

local += (T)arg[c];

この方法では、警告は解消されません。なぜなら、配列の値を21行目の変数の型に合わせる必要があるためです。一見複雑に思えるかもしれませんが、実際には非常にシンプルで明快です。コード02の21行目を、以下のように変更するだけで問題は解決します。

local += (double)arg[c];

これで問題は解決しました。テンプレートとして実装されたAverage関数が完成し、正常に動作します。しかし、同じ問題を解決する別の方法もあります。コードをあまり変更せずに対応する方法です。ここでコード01に立ち返ってみましょう。コード02は、コード01の実装をテンプレート化した結果を示しています。先ほどのように問題がローカル変数によって発生している場合、もっと大胆な方法を試すことも可能です。つまり、コンパイラに任せて自動的に処理させるという考え方です。少し大胆に思えるかもしれません。

しかし、コード01がコード02にどのように変換され、コンパイラの警告をどのように処理したのかを理解している場合、別の解決策を思いつくはずです。そのひとつが、コード02の18行目で宣言されている変数の型自体を変更してしまう方法です。これは非常に興味深い解決策であり、実際の実装過程でもこのアプローチが採用されています。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #define PrintX(X) Print(#X, " :: ", X)
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     const double d_values[] = {2.5, 3.4, 1.25, 2.48, .85, 3.75};
09.     const long i_values[] = {8, 3, 4, 6, 4, 5, 7};
10.     
11.     PrintX(Averange(d_values));
12.     PrintX(Averange(i_values));
13. }
14. //+------------------------------------------------------------------+
15. template <typename T>
16. T Averange(const T &arg[])
17. {
18.     T local = 0;
19. 
20.     for (uint c = 0; c < arg.Size(); c++)
21.         local += arg[c];
22. 
23.     return local / arg.Size();
24. }
25. //+------------------------------------------------------------------+

コード03

コード03は、図01と同じ結果を返し、かつコンパイラから警告も発生しません。なぜでしょうか。これは、コンパイラが変数の型を認識して適切に調整するためです。もしコンパイラが整数型を求めていると判断すれば、変数の型を整数型に合わせます。逆に浮動小数点型を求めている場合は、18行目で宣言された変数を浮動小数点型として使用できるように自動的に調整します。

そのため、コード01よりもはるかにシンプルなコードが得られます。コード02と非常に似ていますが、細かい調整をあれこれおこなう必要はありません。これにより型変換も自動的におこなわれ、コンパイラーの警告も発生しません。とても便利です。もしこれに類似したコードを既に見たことがある方であれば、どのように動作するか想像できると思います。しかし、基本概念を理解し、全体の仕組みを把握すれば、以前は複雑に思えたコードもより簡単で興味深く感じられるようになります。

さて、ここまでが最も楽しい部分でした。そして次に、本当に興味深い部分を見ていきます。コード03で示したユースケースを踏まえ、今度は他のデータ型でも試してみたいと思います。

これまでの例では、union(共用体)で使用されるデータ型のみを示しましたが、コード03で用いた手法を応用すれば、さらに大きなテンプレートを作成することが可能です。ただし、タスクを正しく分割するため、この話題は別の記事で扱います。


union内の要素の利用

まず、コード03で示した内容を、少なくとも最初はもう少し簡単な形で確認します。実際にはコード03自体を再度変更するわけではありませんが、この概念を使ってunion内に動的なメモリ領域を作成します。言い換えれば、配列と任意のデータ型の間にunionを作成するためのテンプレートを作るということです。

unionの定義方法を学んだ際、unionは固定サイズのブロックで構成されることを確認しました。このブロックはバイト単位で定義され、union内で最も大きい型に属するバイト数がその値になります。しかし、これは絶対的なルールではありません。コンパイラーにより、このunionを動的に構成することも可能です。これにより、基本的に動的なブロックサイズを持つunionを作成できます。バイト幅はコードのコンパイル時に決定され、その時点で固定サイズとなります。

ここで、これが非常に難しい作業のように感じる方もいるかもしれません。一見すると、コンパイラにどのようにこの手法を実装させるのか分からないでしょうし、今すぐ必要なのか疑問に思う方もいるかもしれません。確かに、この概念は一見高度に思えるかもしれません。しかし私の考えでは、初心者であっても知っておくべき基本概念です。なぜなら、この知識があるとさまざまなコードをより柔軟に、広範囲の目的で作成できるようになるからです。

多くの方は、進むスピードが速すぎると感じるかもしれません。しかし、私たちは可能な限りゆっくり、ひとつの概念も見逃さずに進めています。この基礎知識がしっかり身につけば、その後の内容も自然に理解できるようになります。とはいえ、将来的に扱う資料は最初は少し混乱するかもしれません。そこで、まずはより簡単なコードから始め、学習を楽しく、理解しやすくする方針です。この考え方に沿って、以下のコードから始めていきます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const ulong info = 0xA1B2C3D4E5F6789A;
07. 
08.     PrintFormat("Before modification: 0x%I64X", info);
09.     PrintFormat("After modification : 0x%I64X", Swap(info));
10. }
11. //+------------------------------------------------------------------+
12. ulong Swap(ulong arg)
13. {
14.     union un_01
15.     {
16.         ulong   value;
17.         uchar   u8_bits[sizeof(ulong)];
18.     }info;
19. 
20.     info.value = arg;
21. 
22.     PrintFormat("The region is composed of %d bytes", sizeof(info));
23. 
24.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
25.     {
26.         tmp = info.u8_bits[i];
27.         info.u8_bits[i] = info.u8_bits[j];
28.         info.u8_bits[j] = tmp;
29.     }
30. 
31.     return info.value;
32. }
33. //+------------------------------------------------------------------+

コード04

このコードでは、まだテンプレートは使用されていません。テンプレートの使用方法は、unionの話題を紹介した別の記事ですでに説明済みのため、ここで再度解説する必要はないと考えています。しかし、コード04を紹介した際には、拡張することには関心がなかったため、12行目の関数は実際には存在していませんでした。すべてはOnStart内で処理されていました。しかし、今回はunionにおけるテンプレートについて扱うため、すべてを別のブロックに分離することが適切です。これにより、unionテンプレートとの初めての接触が容易になります。その後、最初におこなったように、すべてを1つのコードチャンクにまとめることも可能です。

コード04をコンパイルして実行すると、MetaTrader 5のターミナルに結果が表示されます。

図03

言い換えると、ビット操作をおこなっているだけです。非常にシンプルです。では、他のデータ型のビットを変更するにはどうすればよいでしょうか。例えば、ushortのような2バイトの型を12行目の関数に渡すと、誤った結果、もしくは少なくとも不自然な結果が返ってきます。これを確認するには、コード04の6行目を以下のように変更してください。

const ushort info = 0xCADA;

この状態でコード04を実行すると、MetaTrader 5のターミナルに表示される結果は以下の通りです。

図04

ここで図04に注目してください。赤でハイライトした部分です。これはushort型の値に追加された不要なデータです。回転が正しくおこなわれていることは確認できますが、これらのバイトは問題となります。特に、ushort型を保存する必要がある場合、この余分なデータが問題になります。ここでテンプレートが役立ちます。

前回のトピックでも示した通り、さまざまな型の値を扱うことは非常に簡単で、実用的かつ安全です。これにより平均値を計算することも可能です。実際、アプリケーションは整数値と浮動小数点値の両方を問題なく扱えます。しかし、ここでunionを使用してコードを簡略化したい場合、同じことをどのように実現するのでしょうか。それが最も興味深い部分です。

まず、コード04をテンプレートに対応したコードに変換する必要があります。これまでに示した内容を理解していれば、この変換は非常に簡単です。概念を正しく理解していれば、以下に示すコードを問題なく作成できます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const ushort info = 0xCADA;
07. 
08.     PrintFormat("Before modification: 0x%I64X", info);
09.     PrintFormat("After modification : 0x%I64X", Swap(info));
10. }
11. //+------------------------------------------------------------------+
12. template <typename T>
13. T Swap(T arg)
14. {
15.     union un_01
16.     {
17.         ulong   value;
18.         uchar   u8_bits[sizeof(ulong)];
19.     }info;
20. 
21.     info.value = arg;
22. 
23.     PrintFormat("The region is composed of %d bytes", sizeof(info));
24. 
25.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
26.     {
27.         tmp = info.u8_bits[i];
28.         info.u8_bits[i] = info.u8_bits[j];
29.         info.u8_bits[j] = tmp;
30.     }
31. 
32.     return info.value;
33. }
34. //+------------------------------------------------------------------+

コード05

しかし、コード05をコンパイルしようとすると、コンパイラーから警告が発生します。この警告は以下の図の通りです。

図05

ここで少し考えてみましょう。この警告は、32行目の値が戻される型と互換性がないことを示しています。なぜなら、両者の型が異なるためです。この現象をどこかで見たことはないでしょうか。この記事の少し前を振り返ると、図02で同じような警告が出ていたことを確認できます。このことから、この問題を解決する手段はすでに理解できているはずです。解決方法はいくつかあります。しかし、前のスレッドで示したように、コードの変更を最小限に抑えつつ安全に解決する最適な方法は、返却時に期待される型に変数を変換する方法です。つまり、32行目を変更する必要はなく、15行目の値のunionが宣言されている箇所を変更するだけで十分です。

では、なぜ17行目を修正するべきとは言わないのでしょうか。確かに17行目には32行目で使用される変数が存在しています。しかし、ここで17行目のみを変更しても、18行目の配列は変更されません。そのため、問題の一部は修正されますが、unionの幅を変更しない限り、問題が別の場所に移動するだけになります。このように、プログラムのどの部分を正確に修正する必要があるかを理解することが重要です。注意深く対応すれば、問題は発生せず、コードはより汎用的で便利になり、より多くのケースに対応できるようになります。それも、より少ないリスクと労力で実現可能です。

必要な手順を確認したところで、コード05を以下のように変更できます。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const ushort info = 0xCADA;
07. 
08.     PrintFormat("Before modification: 0x%I64X", info);
09.     PrintFormat("After modification : 0x%I64X", Swap(info));
10. }
11. //+------------------------------------------------------------------+
12. template <typename T>
13. T Swap(T arg)
14. {
15.     union un_01
16.     {
17.         T     value;
18.         uchar u8_bits[sizeof(T)];  
19.     }info;
20. 
21.     info.value = arg;
22. 
23.     PrintFormat("The region is composed of %d bytes", sizeof(info));
24. 
25.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
26.     {
27.         tmp = info.u8_bits[i];
28.         info.u8_bits[i] = info.u8_bits[j];
29.         info.u8_bits[j] = tmp;
30.     }
31. 
32.     return info.value;
33. }
34. //+------------------------------------------------------------------+

コード06

コード06を実行すると、コード05を修正しただけのこのコードから、以下の図のような結果が得られます。

図06

まさに完璧です。これで正しい結果が得られました。さらに、コード04をより高度なコードに変換することができました。コンパイラがオーバーロードされた関数や手続きを自動で生成してくれるため、どのデータ型を使用しても問題ありません。コード06のテンプレートを基に、コンパイラは適切な手続きを作成し、常に期待通りの結果を返すように処理をおこなってくれます。

ここまでで、より興味深いモデルを作成したため、これでunionテンプレートの作り方が分かったと思われるかもしれません。確かに、単純なケースではこれで作成方法が理解できます。しかし、ここで示した内容はunionテンプレートそのものではありません。あくまで、関数や手続き内でテンプレートを使用する際のunionの適用可能性についての話です。真のunionテンプレートを作成するためには、さらに少し深い理解が必要です。しかし、テーマを正しく分けるため、この内容は別の新しいスレッドで解説します。こうすることで、混同のリスクを避けることができます。


unionテンプレートの定義

ここからは、テンプレートの扱いについて少し踏み込んで解説します。これまで見てきた内容とは異なり、union用のテンプレートを定義する場合や、後ほど扱う他の型でも同様ですが、初心者プログラマが気にするような判断、すなわちコードをどのように実装すべきかという点を考慮する必要があります。これは、作成するコードの一部が、やや特殊に見えることがあるためです。

コードが複雑すぎるように感じた場合は、落ち着いて一歩引くことをお勧めします。理想的には、ここまで示した内容をしっかり学び、練習して原理や概念を正しく理解してから、前回止めたところに戻って再開するのが良いでしょう。

特定のリソースを使ったコードを見ただけで、理解できたと思い込まないようにしてください。すべてが期待通りに動作するわけではありません。この概念を正しく理解することが、コードをより簡単で明確に扱う第一歩です。 

ここからおこなう内容は、早い段階では少し混乱を招くかもしれません。この混乱については次回の記事で詳しく解説します。ただし、今回示す内容は必要な処理であり、このようなケースに遭遇した場合や、今回のような方法で実装する必要がある場合には、何が起こるのか理解しておく必要があります。

いつものように、簡単なことから始めましょう。目標は、前のセクションで示したコード06に近い内容です。ただし、関数や手続きは使用しません。これは後ほど、少し難しい内容として解説します。したがって、この段階ではすべてOnStart関数内で処理します。それでは始めましょう。まず、以下にコードを示します。

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

コード07

コード07を実行すると、結果はコード04と非常によく似ています。ただし、ここでは2つの値を同時に入力して、すべてを一度に表示しています。そのため、コード07を実行した際の実際の結果は、以下の通りです。

図07

図04で、最終結果に不適切な値が混入していたことを確認しましたが、ここ図07でも同じ値が現れています。簡単に言うと、ushort型の変数になり得る値がありますが、他の不適切な値によって影響を受けています。最初はこのコードが正しいように見えるかもしれませんし、実際に正しい場合もあります。しかし、この学習の目的は、前のセクションで得られた結果に近い結果を得られるように、テンプレートを使った実装方法を理解することにあります。

ただし、今回は関数や手続きは使用しません。もう一つ注目すべき点として、関数や手続きを使わないために、コードの共通部分をマクロとして渡しています。これは7行目以降で確認できます。マクロの動作についてはすでに説明済みのため、ここでは詳しくは触れません。

それでは、本題に進みましょう。前のスレッドとは異なり、今回は少し異なる状況に対処しますが、目標は同じです。以前適用できた概念は、ここではそのまま適用できません。これは、コード07にはテンプレートを適用できる関数や手続きが存在しないためです。しかし、ここで適用する基本概念は、以前使用したものと非常によく似ており、最初はやや混乱するだけです。

注意してください。今回の目標は、コード06で作成したものと同様に、任意のデータ型で動作するコードを作成することです。この目標を達成するために、これまでとは少し異なる種類のオーバーロードを使用する必要があります。これまでオーバーロードは関数や手続きに適用してきましたが、今回は型のオーバーロードが必要です。一見すると少し混乱するかもしれませんが、基本概念は関数や手続きのオーバーロードを使用する場合と同じです。

次に注目すべき点は、コード07の14行目に宣言されたunionを、コード06で使用したunionと同じように機能させる必要があることです。では、どのようにおこなうのでしょうか。とても簡単です。14行目のunionをテンプレート化するだけです。やり方がわからないと思われるかもしれません。確かに、このようなシミュレーションに初めて触れる場合は、少し混乱するかもしれません。まず、14行目で確認できるリンクはローカルです。そして、すべてのテンプレートはグローバルである必要があります。そのため、最初にOnStart内の宣言を削除し、宣言をグローバルモデルに変換します。その後、関数や手続きでおこなったように、unionをテンプレートに変換します。これにより、コード08のような形が完成します。

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

コード08

ここまでの内容は非常に簡単で分かりやすく、すでに理解している範囲の内容に沿ったものでした。しかし、ここからが難しい部分です。コード08をコンパイルしようとすると、コンパイルに失敗します。これは、コンパイラの観点から見て、初心者には理解が難しい種類のエラーが含まれているためです。コンパイルを試みた際にコンパイラーが出力する情報は、以下の通りです。

図08

一見すると、このエラーの多さに心が折れそうになります。しかし、ここに問題の核心があります。多くの初心者が混乱するポイントは、まさにこの部分です。図08で確認できるすべてのエラーは、コンパイラーがどのデータ型を使用すべきか分からないという状況で発生しています。この記事ではすでに多くのトピックについて説明をおこなっており、ここでこの問題の解決策を示すと混乱を招く可能性があるため、今回は一旦ここで区切ります。この問題の解説は、次回の記事でおこないます。


最終的な考察

本記事では、非常に難しいトピックの探求を始めました。ただし、もちろん、ここで示した内容を実際に練習していれば、理解は可能です。しかし、皆さんにしっかり学習してほしいと思い、今回は記事の最後で扱ったテーマを一旦中断することにしました。コンパイラにどのように指示を与えるかを正しく説明するには時間がかかりますし、すでに十分複雑なテーマにさらに混乱を加えたくないためです。

そのため、まずは本記事で示した内容を実際に練習してみてください。そして、コード08を正しく動作させるために、コンパイラにどのデータ型を使うべきかを伝える方法を考えてみてください。次回の記事では、実際にどのようにおこなうか、さらにそれをどのように活用するかを解説しますので、落ち着いて考えておくと良いでしょう。

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

添付されたファイル |
Anexo.zip (4.04 KB)
最後のコメント | ディスカッションに移動 (2)
Denis Kirichenko
Denis Kirichenko | 15 8月 2025 において 09:54
おそらくAverange()ではなく、Average()だろう...。
Ilya Prozumentov
Ilya Prozumentov | 15 8月 2025 において 23:26
テンプレートで実装されたユニオンを宣言する際には、コンパイラにデータ型を指定する必要があると思います。
un_01 <ulong> info;
循環単為生殖アルゴリズム(CPA) 循環単為生殖アルゴリズム(CPA)
本記事では、新しい集団最適化アルゴリズムである循環単為生殖アルゴリズム(CPA: Cyclic Parthenogenesis Algorithm)を取り上げます。本アルゴリズムは、アブラムシ特有の繁殖戦略に着想を得ています。CPAは、単為生殖と有性生殖という2つの繁殖メカニズムを組み合わせるほか、個体群のコロニー構造を活用し、コロニー間の移動も可能にしています。このアルゴリズムの主要な特徴は、異なる繁殖戦略間の適応的な切り替えと、飛行メカニズムを通じたコロニー間の情報交換システムです。
アルゴリズム取引におけるニューロシンボリックシステム:シンボリックルールとニューラルネットワークを組み合わせる アルゴリズム取引におけるニューロシンボリックシステム:シンボリックルールとニューラルネットワークを組み合わせる
本記事では、古典的なテクニカル分析とニューラルネットワークを組み合わせたハイブリッド型取引システムの開発経験について解説します。システムのアーキテクチャを、基本的なパターン分析やニューラルネットワーク構造から、実際の売買判断に至るメカニズムまで詳細に分析し、実際のコードや実務的な知見も共有します。
MetaTrader 5での取引の視覚的な評価と調整 MetaTrader 5での取引の視覚的な評価と調整
ストラテジーテスターは、単に自動売買ロボットのパラメータを最適化するだけでなく、さらに幅広い活用が可能です。本記事では、口座の取引履歴を事後に評価し、ストラテジーテスター上でポジションのストップロスを変更することで取引の調整をおこなう方法を紹介します。
学習中にニューロンを活性化する関数:高速収束の鍵は? 学習中にニューロンを活性化する関数:高速収束の鍵は?
本記事では、ニューラルネットワークの学習における異なる活性化関数と最適化アルゴリズムの相互作用に関する研究を紹介します。特に、古典的なADAMとその集団版であるADAMmを比較し、振動するACONやSnake関数を含む幅広い活性化関数での動作を検証します。最小構成のMLPアーキテクチャ(1-1-1)と単一の学習例を用いることで、活性化関数が最適化に与える影響を他の要因から切り離して観察します。本記事では、活性化関数の境界を利用したネットワーク重みの管理と重み反射機構を提案し、学習における飽和や停滞の問題を回避できることを示します。