読者です 読者をやめる 読者になる 読者になる

使用するメンバ関数をクラスに通知する(ようにみせかける)方法

このコードを見てほしい
printは使用するメンバ関数を出力し、funcは適当な処理を行う

int main(){
	Foo foo;
	foo.print(); //func1 func2 func3

	foo.func1();
	foo.func3();
	foo.func2();
}

出力>>func1 func2 func3

int main(){
	Foo foo;
	foo.print(); //func2 func3

	//foo.func1();
	foo.func3();
	foo.func2();
}

出力>>func2 func3

int main(){
	Foo foo;
	foo.print(); //func3

	//foo.func1();
	foo.func3();
	//foo.func2();
}

出力>>func3

これら3つのソースコードでFooの変更はしていない
また、funcが出力しているわけではない

しかしprintはこれから使用される関数を知っているかのように出力する……


答え
テンプレートで関数のインスタンス化を抑制し
関数内でstatic変数をもつテンプレートクラスを生成し
static変数はコンストラクタで適当に処理を行い
あたかもこれから使われる関数を知っているかのように見せかける


template<class T>
class Piyo
{
public:
	void print()
	{
		for (auto&x : get())
		{
			std::cout << x << std::endl;
		}
	}

	static std::vector<std::string>&get()
	{ 
		static std::vector<std::string> p;
		return p;
	}

	template<std::string(*CALL)()>
	struct Hoge{
		Hoge(){x;}
		struct XXX{	XXX(){	get().push_back(CALL()); }	};
		static XXX x;
	};
	void func1(){Hoge<_1> c;}
	void func2(){Hoge<_2> c;}
	void func3(){Hoge<_3> c;}
	static std::string _1(){ return"func1"; }
	static std::string _2(){ return"func2"; }
	static std::string _3(){ return"func3"; }
};

template<class T>template<std::string(*CALL)()>
typename Piyo<T>:: template Hoge<CALL>::XXX
Piyo<T>::Hoge<CALL>::x;
using Foo = Piyo<int>;

関数の生成を抑制すれば使われない関数は実体化しない
インスタンス化した関数はテンプレートクラスをインスタンス
インスタンス化したテンプレートクラスはstatic変数を持ち
static変数はコンストラクタで色々悪さする

というギミック。使い道は現状ない
ラムダ式をテンプレート引数にできるようになればもうすこしスマートに書けるし
関数内で明示的なテンプレートのインスタンス化できればもう少し気持ちい感じになるかもしれない

ちなみにVisualStudio2013でのみ動作を確認
ギミック的にはほかのコンパイラでも動きそうな気がするが他のコンパイラだと

template<class T>template<std::string(*CALL)()>
typename Piyo<T>:: template Hoge<CALL>::XXX
Piyo<T>::Hoge<CALL>::x;

の部分がひっかかって構文エラーを起こす。こんな構文普段書かないから何が正解かさっぱりわからん
またhogeコンストラクタのxは削除するとXXXがインスタンス化されない

上の部分のC++的な正しい書き方、知ってる人がいらっしゃいましたら是非教えてください