一汁三菜

自分が楽しいと思うこと、マラソン、旅行、その他日々の記録をしたい。

type erasure

type erasureとは、C++みたいな静的型付け言語でduck typingもどきを実現する為のテンプレート魔術の事。ぐぐると色々出てくる。

単に色んな型を渡せるようにするだけならば、メンバ関数テンプレートでOK。

#include <iostream>
class Foo {
public:
	template<class T>
	void func(T &x) {
		std::cout << x.getName() << std::endl;
	}
};

メンバ関数テンプレートだけで事足りなくなるのは、func()のT型の引数をメンバ変数に保存しようとした時。メンバ変数の型が書けない。

これを実現するのがtype erasure。T型の変数を保持しておくテンプレートクラスを用意する事によって、T型の変数を保持する事が出来る。

#include <iostream>
#include <string>

class BaseHolder {
public:
  virtual ~BaseHolder(){}
  virtual std::string GetName(void) const = 0;
};

// BaseHolderを継承するテンプレートクラス
// T型の引数はこのクラスに保存できる
template<class T>
class FooHolder : public BaseHolder {
  T obj;
public:
  FooHolder(const T &x) : obj(x) {}
  virtual ~FooHolder() {}
  virtual std::string GetName(void) const { return obj.GetName(); }
};

// duck typingもどきをするメンバ関数テンプレートを持つクラス
class Foo {
  BaseHolder *obj;
public:
  Foo() : obj(NULL) {
  }
  virtual ~Foo() {
    if(obj) delete obj;
  }
  template<class T>
  void Set(const T &x) { 
    obj = new FooHolder<T>(x); 
  }
  void Call(void) {
    std::cout << obj->GetName() << std::endl;
  }
};

これで以下のコードが書けるようになります。

// メンバ関数 GetName() を持つクラスその1
class A {
public:
  std::string GetName(void) const { return "Class A"; }
};

// メンバ関数 GetName() を持つクラスその2
class B {
public:
  std::string GetName(void) const { return "Class B"; }
};

int main(void)
{
  Foo obj;
  A a;
  B b;

  obj.Set(a);
  obj.Call();

  obj.Set(b);
  obj.Call();

  return 0;
}