関数テンプレート

オーバーロードされた関数は一般的に様々なデータ型に対して同様の演算を行うために使用されます。ArraySize() は MQL5 でのそのような関数の簡単な例で、配列のサイズを返します。実際には、このシステム関数はオーバーロードされており、このようなオーバーロードの実装は全体的に MQL5 アプリケーションの開発者から隠されています。

int  ArraySize(
  void&  array[]      // チェックされた配列
  );

つまり MQL5 言語コンパイラはこの関数の呼び出しごとに必要な実装を挿入するわけです。例えば、整数型の配列には下記が行われます。

int  ArraySize(
  int&  array[]      // int 型要素の配列
  );

ArraySize() は履歴データ形式の相場を操作するための MqlRates 型の配列にしては次のように表示されます。

int  ArraySize(
  MqlRates&  array[] // MqlRates 型の配列
  );

よって、同様の関数を異なる型の演算をするため使用するのが非常に便利です。しかし、準備作業が必要です。つまり、必要な関数は演算される全てのデータ型に対してオーバーロードされるべきです。

これには便利な解決法があります。それぞれのデータ型に同様の演算が実行される場合には、関数テンプレートの使用が可能です。この場合、プログラマは 1 つの関数テンプレートの記述をする必要があります。テンプレートを記述する際は、関数が作業する明確なデータ型の代わりに一部のみの仮パラメータが指定されます。コンパイラは関数が呼び出される時に使用される引数の型に基づいて自動的に各データ型の適切な取り扱いのための関数を生成します。

関数テンプレートの定義は template キーワードで始まり角括弧内の仮パラメータのリストが続きます。仮パラメータには typename キーワードが先行します。仮パラメータ型は組み込み型またはユーザ定義型です。用例は次の通りです。

  • 関数の引数の型を指定する
  • 関数の戻り値の型を指定する
  • 関数定義内で変数を宣言する

 

テンプレートパラメータの数は8を超えることは出来ません。テンプレート定義の各仮パラメータは、少なくとも 1 度は関数パラメータのリストに表示されるべきです。仮パラメータの名称は一意である必要があります。

数値型(整数や実数)の配列で最高値を検索する関数テンプレートの例は次の通りです。

template<typename T>
T ArrayMax(T &arr[])
 {
  uint size=ArraySize(arr);
  if(size==0) return(0);          
 
  T max=arr[0];
  for(uint n=1;n<size;n++)
    if(max<arr[n]) max=arr[n];
//---
  return(max);
 }

このテンプレートは、渡された配列の最高値を求める関数を定義し、この値を結果として返します。MQL5 に内蔵された ArrayMaximum() 関数は最高値のインデックスを返すので、それから値そのものを見つけることが可能になります。例:

//--- 配列を作成する
  double array[];
  int size=50;
  ArrayResize(array,size);
//---  ランダムな値を書き込む
  for(int i=0;i<size;i++)
    {
     array[i]=MathRand();
    }
 
//---配列内の最大値のインデックスを見つける
  int max_position=ArrayMaximum(array);
//--- 配列内の最大値を取得する
  double max=array[max_position];
//--- 見つけた値を表示する
  Print("Max value = ",max);

従って、配列の最大値を取得するのに必要な 2 つのステップが実行されました。ArrayMax() 関数テンプレートに適切な型の配列を渡すだけで必要な型の結果を得ることが出来ます。最後の 2 行

//---配列内の最大値のインデックスを見つける
  int max_position=ArrayMaximum(array);
//--- 配列内の最大値を取得する
  double max=array[max_position];

は、返される結果が関数に渡された配列と同じ型を持っている1行に出来ます。

//--- 最大値を見つける
  double max=ArrayMax(array);

この場合、ArrayMax() 関数によって返される結果の型は自動的に配列の型と一致されます。

 

さまざまなデータ型を演算出来る汎用メソッドを作成するために引数の型を文字列として取得するには typename キーワードを使用します。データ型を文字列として返す関数の具体例を考えてみましょう。

#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  CTrade trade;  
  double d_value=M_PI;
  int i_value=INT_MAX;
  Print("d_value: type=",GetTypeName(d_value), ",   value=", d_value);
  Print("i_value: type=",GetTypeName(i_value), ",   value=", i_value);
  Print("trade: type=",GetTypeName(trade));
//---
 }
//+------------------------------------------------------------------+
//| 型が文字列として返される                                               |
//+------------------------------------------------------------------+
template<typename T>
string GetTypeName(const T &t)
 {
//--- 型を文字列として返す
  return(typename(T));
//---
 }

 

関数テンプレートはクラスメソッドにも使用することが出来ます。例えば、

class CFile
 {
  ...
public:
  ...
  template<typename T>
  uint WriteStruct(T &data);
 };
 
template<typename T>
uint CFile::WriteStruct(T &data)
 {
  ...
  return(FileWriteStruct(m_handle,data));
 }

関数テンプレートは exportvirtual 及び #import キーワードと宣言されるべきではありません。

テンプレート関数の多重定義(オーバーロード)

テンプレート関数の多重定義はしばしば必要です。例えば型キャスティングを使用して1番目のパラメータに2番目のパラメータの値を書き込むテンプレート関数があるとします。MQL5ではstringからboolへの型キャスティングは不可能です。しかし、テンプレート関数の多重定義を作成すれば、それは可能になります。例は下記です。

//+------------------------------------------------------------------+
//| テンプレート関数                                                      |
//+------------------------------------------------------------------+
template<typename T1,typename T2>
string Assign(T1 &var1,T2 var2)
 {
  var1=(T1)var2;
  return(__FUNCSIG__);
 }
//+------------------------------------------------------------------+
//| bool+stringのための特別な多重定義                                     |
//+------------------------------------------------------------------+
string Assign(bool &var1,string var2)
 {
  var1=(StringCompare(var2,"true",false) || StringToInteger(var2)!=0);
  return(__FUNCSIG__);
 }
//+------------------------------------------------------------------+
//| スクリプトプログラムを開始する関数                                          |
//+------------------------------------------------------------------+
void OnStart()
 {
  int i;
  bool b;
  Print(Assign(i,"test"));
  Print(Assign(b,"test"));
 }

コード実行の結果、Assign()テンプレート関数はint+stringのペアで使用されていますが、多重定義されたバージョンは2回目の呼び出しでbool+stringのペアですでに使用されています。

string Assign<int,string>(int&,string)
string Assign(bool&,string)

参照

多重定義(オーバーロード)