PR

【MQL5】引数の型を動的にするtemplate – オリジナルArrayFind関数

オリジナル関数

はじめに

MQL5でEAやインジケーターを開発していると、同じロジックを異なるデータ型で使い回したい場面が頻繁に出てきます。例えば、配列内で特定の値を検索する処理は、int型、double型、string型など、様々な型で必要になります。

従来のMQL5では型ごとに別々の関数を定義する必要がありましたが、テンプレート(template)機能を使うことで、1つの関数定義で複数の型に対応できるようになります。(ジェネリック関数)

本記事では、MQL5のテンプレート機能の基本概念から実践的な使い方まで、ArrayFind関数の実装を通して詳しく解説します。

WAN
WAN

チャートのテンプレート(.tpl)ではないよ(‘Д’)

テンプレートとは?

基本概念

テンプレートとは、型をパラメータとして受け取る関数や構造体の雛形です。コンパイル時に実際の型が決定され、その型に応じた関数が自動生成されます。

// テンプレートの基本構文
template<typename T>
戻り値の型 関数名(引数...)
{
    // 関数の実装
}

MQL5でのテンプレート宣言

MQL5では以下の方法でテンプレートを宣言できます:

template<typename T>          // 最も一般的
template<class T>             // typename と同じ意味
template<typename T, typename U>  // 複数の型パラメータ

従来の方法とテンプレートの比較

従来の問題点

型別に関数を定義する従来の方法では、以下のような課題がありました:

// 整数用
int ArrayFindInt(int &array[], int value, int start = 0) 
{
    int size = ArraySize(array);
    for(int i = start; i < size; i++)
    {
        if(array[i] == value) return i;
    }
    return -1;
}

// 文字列用  
int ArrayFindString(string &array[], string value, int start = 0) 
{
    int size = ArraySize(array);
    for(int i = start; i < size; i++)
    {
        if(array[i] == value) return i;
    }
    return -1;
}

// double用
int ArrayFindDouble(double &array[], double value, int start = 0) 
{
    int size = ArraySize(array);
    for(int i = start; i < size; i++)
    {
        if(array[i] == value) return i;
    }
    return -1;
}

問題点:

  • ❌ コードの重複:同じロジックを型ごとに記述
  • ❌ 保守性の低下:修正時に全ての関数を更新する必要
  • ❌ 関数名の複雑化:型別に異なる関数名を記憶
  • ❌ 型の追加時:新しい型用の関数を再実装
WAN
WAN

同じことをしている関数なのに、型が違うだけで再定義しないといけないのはとてもナンセンス(‘Д’)

テンプレートによる解決

template<typename T>
int ArrayFind(T &array[], T match_value, int start_pos = 0)
{
    int array_size = ArraySize(array);
    if(start_pos < 0) start_pos = 0;
    if(start_pos >= array_size) return -1;

    for(int i = start_pos; i < array_size; i++)
    {
        if(array[i] == match_value) return i;
    }
    return -1;
}

テンプレートの利点:

  • ✅ コードの統一:1つの定義で全ての型に対応
  • ✅ 型安全性:コンパイル時に型チェック
  • ✅ 保守性向上:1箇所の修正で全型に適用
  • ✅ 直感的使用:どの型でも同じ関数名で使用可能
  • ✅ 自動型推論:コンパイラが適切な型を自動選択

ArrayFind関数

関数仕様

template<typename T>
int ArrayFind(
   T &array[],           // 検索対象の配列
   T match_value,        // 検索する値
   int start_pos = 0     // 検索開始位置(デフォルト:0)
)

引数:

  • array[]: 検索対象の配列(任意の型T)
  • match_value: 検索する値(配列と同じ型T)
  • start_pos: 検索開始位置(省略可能、デフォルト0)

戻り値:

  • 見つかった場合:配列内の位置(0から始まるインデックス)
  • 見つからなかった場合:-1

ソースコード

//+------------------------------------------------------------------+
//|                                                    ArrayFind.mq5 |
//|                                             Copyright 2025, MQL5 |
//|                              テンプレートを使用したArrayFind関数  |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| ArrayFind テンプレート関数                                        |
//| 動的配列内で指定された値を検索する汎用関数                          |
//+------------------------------------------------------------------+
template<typename T>
int ArrayFind(
   T &array[],           // 検索対象の配列
   T match_value,        // 検索する値
   int start_pos = 0     // 検索開始位置
)
{
   // 配列サイズを取得
   int array_size = ArraySize(array);

   // 開始位置の妥当性チェック
   if(start_pos < 0)
      start_pos = 0;
   if(start_pos >= array_size)
      return -1;

   // 指定位置から配列の終端まで検索
   for(int i = start_pos; i < array_size; i++)
   {
      if(array[i] == match_value)
         return i;  // 見つかった位置を返す
   }

   return -1;  // 見つからなかった場合
}

//+------------------------------------------------------------------+
//| 補助関数:配列の全要素を表示                                       |
//+------------------------------------------------------------------+
template<typename T>
void PrintArray(T &array[], string array_name = "Array")
{
   Print("型名: ", typename(T));
   int size = ArraySize(array);
   string output = array_name + "[" + IntegerToString(size) + "]: ";

   for(int i = 0; i < size; i++)
   {
      output += (string)array[i];
      if(i < size - 1) output += ", ";
   }
   Print(output);
}
WAN
WAN

typename(T)をPrintで出力することで受け取った引数の型が何かまでわかっちゃう!!

サンプルコード

基本的な使用法

void OnStart()
{
   Print("=== ArrayFind テンプレート関数のテスト ===");

   // 整数配列での検索
   int numbers[] = {10, 20, 30, 40, 50, 30, 60};
   PrintArray(numbers, "整数配列");

   int result = ArrayFind(numbers, 30);
   Print("30を検索した結果: ", result, " (値: ", numbers[result], ")");

   // 文字列配列での検索
   string symbols[] = {"EURUSD", "GBPUSD", "USDJPY", "EURJPY"};
   PrintArray(symbols, "通貨ペア配列");

   result = ArrayFind(symbols, "GBPUSD");
   if(result >= 0)
      Print("GBPUSDを検索した結果: ", result, " (値: ", symbols[result], ")");
   else
      Print("GBPUSDは見つかりませんでした");

   // double配列での検索
   double prices[] = {1.2345, 1.2350, 1.2340, 1.2355};
   PrintArray(prices, "価格配列");

   result = ArrayFind(prices, 1.2350);
   if(result >= 0)
      Print("1.2350を検索した結果: ", result, " (値: ", DoubleToString(prices[result], 4), ")");
}

開始位置を指定した検索

void TestStartPosition()
{
   Print("\n=== 開始位置指定検索のテスト ===");

   int numbers[] = {10, 20, 30, 40, 30, 50, 30};
   PrintArray(numbers, "重複値を含む配列");

   int first = ArrayFind(numbers, 30);        // 最初から検索
   int second = ArrayFind(numbers, 30, 3);    // 3番目以降から検索
   int third = ArrayFind(numbers, 30, 5);     // 5番目以降から検索
   int notFound = ArrayFind(numbers, 30, 7);  // 配列外から検索

   Print("最初の30の位置: ", first);
   Print("3番目以降の30の位置: ", second);  
   Print("5番目以降の30の位置: ", third);
   Print("7番目以降の30の位置: ", notFound, " (見つからない)");
}

テンプレートの応用例

1. 複数型パラメータのテンプレート

template<typename T, typename U>
bool ArrayContains(T &array[], U search_value)
{
   return ArrayFind(array, (T)search_value) >= 0;
}

void TestMultipleTypes()
{
   double prices[] = {1.2345, 1.2350, 1.2340};

   // int型の値をdouble配列で検索
   bool found = ArrayContains(prices, 1);  // 自動的に1.0に変換
   Print("1をdouble配列で検索: ", found);
}

2. 構造体でのテンプレート使用

struct PriceData
{
   datetime time;
   double price;
   int volume;
};

template<typename T>
struct ArraySearchResult
{
   int index;
   T value;
   bool found;
};

template<typename T>
ArraySearchResult<T> ArrayFindWithResult(T &array[], T match_value)
{
   ArraySearchResult<T> result;
   result.index = ArrayFind(array, match_value);
   result.found = (result.index >= 0);
   result.value = result.found ? array[result.index] : (T)0;
   return result;
}

パフォーマンスとベストプラクティス

1. コンパイル時の最適化

テンプレートはコンパイル時に展開されるため、実行時のオーバーヘッドがありません:

// これらの呼び出しは...
ArrayFind(int_array, 30);
ArrayFind(string_array, "EURUSD");

// コンパイル時に以下のような個別関数に展開されます
int ArrayFind_int(int &array[], int match_value, int start_pos);
int ArrayFind_string(string &array[], string match_value, int start_pos);

2. 型制約の追加

template<typename T>
int ArrayFindNumeric(T &array[], T match_value, int start_pos = 0)
{
   // 数値型のみに制限したい場合のコメント
   // この例では型チェックは実装していませんが、
   // 使用時に適切な型を渡すことが重要です

   static_assert(sizeof(T) > 0, "型Tは有効な型である必要があります");

   return ArrayFind(array, match_value, start_pos);
}

3. エラーハンドリングの強化

template<typename T>
int ArrayFindSafe(T &array[], T match_value, int start_pos = 0)
{
   // NULL配列チェック
   if(ArraySize(array) == 0)
   {
      Print("エラー: 配列が空です");
      return -1;
   }

   // 範囲チェック
   if(start_pos < 0 || start_pos >= ArraySize(array))
   {
      Print("エラー: 開始位置が無効です");
      return -1;
   }

   return ArrayFind(array, match_value, start_pos);
}

まとめ

テンプレートの主要なメリット

  1. コードの再利用性: 1つの実装で複数の型に対応
  2. 型安全性: コンパイル時の型チェック
  3. パフォーマンス: 実行時オーバーヘッドなし
  4. 保守性: 単一箇所での修正が全型に適用
  5. 拡張性: 新しい型への自動対応

使用時の注意点

  1. コンパイル時間: 多用するとコンパイル時間が増加
  2. デバッグ: エラーメッセージが複雑になる場合がある
  3. 型制約: 演算子や関数が定義されていない型では使用不可

次のステップ

テンプレートを使いこなすことで、より汎用的で保守性の高いMQL5コードが書けるようになります。今回のArrayFind関数を参考に、以下のような関数もテンプレートで実装してみてください:

  • ArraySort(配列ソート)
  • ArrayMax/ArrayMin(最大値・最小値検索)
  • ArrayUnique(重複除去)
  • ArrayFilter(条件フィルタリング)

MQL5のテンプレート機能を活用して、より効率的で読みやすいコードを目指しましょう!


タイトルとURLをコピーしました