TMPわからん

テンプレーメタプログラミング(TMP)というものをC++においてよく見かけます

私の理解が曖昧なので、とりあえずまとめることにします

前提知識として

C言語C++の言語使用の違いの例として、よく「同一の名前を持つ関数を複数定義できる」ことが挙げられます

(最新のC言語の仕様を知りませんのでC99の話をしています
今もまだそうなのかな。。。?)

これを「関数のオーバーロード」と呼びます

// (1)
// 引数がint型のpow関数
int pow(int x)
{
  return x*x;
}

// (2)
// 引数がdouble型のpow関数
double pow(double x)
{
  return x*x;
}


int main()
{
  int    a;
  double b;

  pow(a); // (1)が呼ばれる
  pow(b); // (2)が(ry
}

上記は参考書などでよく見る例です
どちらもpow(変数)の形で関数を呼び出すことができます

では、関数のオーバーロードが定義されている場合に  プログラムは呼び出す関数を「どの段階」で「どの様に判断」しているのでしょうか?  

これは「コンパイル時」に「与えた変数の型」から推論しているそうです ※要出典
(すでにこれが一種のメタプログラミングのようだ、と筆者は思いました)

変数aはint型なので、関数pow(a)にはint型のaが渡されています。
したがって、引数の型がintのpow関数(1)を呼び出しています

double型の変数bも同様にコンパイル時に調べられて、 呼び出される関数(この場合は(2)の関数)が決定します

もし、int型の引数をとる同名の関数が2つ定義されていた場合は どうなるでしょう?

// (1)
// 引数がint型その1
int pow(int x)
{
  return x*x; 
}

// (2)
// 引数がint型その2
string pow(int x)
{
  return to_string(x)+" ワット";
}

int main()
{
  int a
  string s = pow(a);
}

これはコンパイルエラーになります

引数の型のみでオーバーロード関数の解決を行っているので、
全く同じ引数の組み合わせを持つ関数を同一の名前空間(namespace)上に
2つ以上定義することはできません

この「同一の引数型の組み合わせを持つ同名の関数はオーバーロード解決できない」
という、当たり前で基本的な事のように思える規格は TMPで重要になってきます

最初のpow関数にlong型をさらに増やします

// (1)
int pow(int x)
{
  return x*x;
}

// (2)
double pow(double x)
{
  return x*x;
}

// (3)
// 追加
long pow(long x)
{
  return x*x;
}

int main()
{
  pow(10); // (1)と(3)のどちらが呼ばれる?
}

この3つの関数を定義した状態で関数を呼び出すと、
int型引数(1)とlong型引数(3)のどちらの関数が呼ばれるのでしょうか?

これは、intが呼ばれます
つまり、上記は問題なくオーバーロードが解決されます

引数の型や数が違えば、オーバーロードは解決することができるのです

Template Function

  • テンプレート関数

あとで書く

  • 特殊化

あとで書く

Teplate Meta Programming(TMP)

  • **型引数とtypedef

あとで書く

  • 例1:数値演算

あとで書く

  • SFINAE

sfinaeとは「あるテンプレート関数(あるいはテンプレートクラスのメンバ関数を呼び出し、それがコンパイル時にエラーになるならば、その関数をオーバーロード候補から外す」というもの

たとえば、以下のような関数

// (1)
template<typename T>
auto func(T t) -> typename T::type
{
  t.type_v = "v";
  cout << "(1)" << endl;
  return typename T::type();
}

// (2)
template<typename T>
auto func(T t) -> decltype( t.value )
{
  cout << "(2)" << endl;
  return t.value;
}

struct Cm
{
  //int   value;
  using type = string;
  type type_v;
};

struct Cn
{
  int value;
  //using type = string;
  //type type_v
};

struct Co
{
  double dob;
  //int   value;
  //using type = string;
  //type type_v;
};

struct Cp
{
  int value;
  using type = string;
  type type_v;
};

int main()
{
  Cm Om;
  Cn On;
  Co Oo; 
  Cp Op; 
    
  auto m = func(Om); //(1)が呼ばれる
  auto n = func(On); //(2)が呼ばれる
  //auto o = func(Oo);  //(1)も(2)も実体化されないので、関数呼び出し失敗
  //auto p = func(Op);  //(1)も(2)も実体化されてしまうので、オーバーロード未解決
}

http://melpon.org/wandbox/permlink/Aw7Wm4Ctgq5PzOm6

引数として渡したクラス(構造体)がtypeかvalueのどちらを持っているかで、
呼び出される関数が変化します

funcのテンプレート引数としてtypeを持っているCmを渡した場合に
テンプレート関数func(1)は問題なく呼び出されますが、
テンプレート関数func(2)は(変数Cm.valueを持っていないので)、コンパイル時エラーになります

また、逆にCnでは(Cn::type型が定義されていないので)func(1)がコンパイル時エラーになります

このように、テンプレート関数はコンパイル時に実体化でできないものがあるものの、
他にオーバーロードされる関数の候補が存在する場合に
最新のコンパイラでは、エラーにせずに生成を見送る挙動を示します

これをSFINAEと呼ぶそうです
これを使うと、テンプレート引数に応じて、静的に関数の実装や処理内容を変えることが可能になります

これを利用する事で、引数や型引数によって挙動を変える関数やコンテナクラスをおもしろおかしく作る事ができるようです

今困っていること

ちなみに、なぜこんなことをまとめているかというと 「テンプレートクラスVのテンプレート引数に選んだクラスTがある特定の型の引数ありコンストラクタを持っていた場合に関数Aを、なければ関数Bを実装する」(長い)
というコンストラクタ特性によるTMPを実装しようとして、つまずいているからです

(2014.3.22) 無事、やりたかった事ができました やったね!