meryngii.neta

今日も新たな"ネタ"を求めて。

型消去と引数転送

右辺値参照 + 可変個引数テンプレートでBoost.Functionの真似事をしようと思ったのだが、なんだか面倒な問題にぶち当たってしまったらしい。

#include <utility>

template <typename... Args>
struct my_function
{
	template <typename T>
	struct object_invoker
	{
		static void invoke(void* ptr, Args&&... args)
		{
			(*static_cast<T*>(ptr))
				(std::forward<Args>(args)...);
		}
	};
	
	template <typename T>
	void operator = (T object)
	{
		this->ptr = new T(object);
		this->invoker = &object_invoker<T>::invoke;
	}
	
	template <typename... Args2>
	void invoke(Args2&&... args)
	{
		invoker(ptr, std::forward<Args2>(args)...);
	}
	
	void* ptr;
	void (*invoker)(void*, Args&&...);
};

struct A
{
	void operator() (int) { }
};

int main()
{
	my_function<int> f;
	f = A();
	
	int i1 = 0;
	f.invoke(i1);
	
	const int i2 = 0;
	f.invoke(i2); // エラー
}

様々な関数オブジェクトを使えるようにするために型消去*1と呼ばれているらしいテクニックを使っている。また、引数転送に右辺値参照*2を使っている。右辺値参照はあまりに絶妙なので試すたびに騙されたような気分になる。あと、もちろん可変個引数テンプレートは引数を複数個取るために使っている。
しかしこのコードは期待通りには動かない。よく考え直してみると、型消去のテクニックでinvokerはただの関数ポインタになるので、それ以降は関数テンプレートの引数推論が使えない。つまりそれ以降は引数転送ができないのだが、呼び出される関数はさらに先にあるので、不完全な転送になるのだと思われる。
object_invoker::invokeの引数はArgs2と等しくなるべきだが、実際にはそれはできない。上のコードでは仮に呼び出し先のArgsとしてある。
組み合わせているテクニック同士の相性が悪いのだろうか。それとも私の新機能の認識が根本的に間違っているのだろうか。一体Boostではどうやって実装するのだろう。