c++閹割版binder實現

qiu_hanhanhan發表於2017-07-08

首先分析下binder的原理:為一個仿函式繫結幾個引數,或者用place holder改變引數的順序。首先實現不帶有place holder版本的binder,實現如下功能:一個binder類,構造的時候為仿函式按函式引數順序繫結若干個函式,過載operator(),輸入若干引數,內部呼叫仿函式,返回值為仿函式的返回值。根據功能分析可知,需要使用模板,因為繫結引數型別不一,可能需要一個tuple儲存繫結的引數。如下程式碼所示:

template <typename Func, typename RET, typename... Args>
class binder {
public:
	binder(Func f1, Args... rest) :f(f1), t(rest...){
	}
	template <typename... Rest>
	RET operator ()(const Rest&... rest) ;
private:
	Func f;
	tuple<Args...> t;
};


重要的是實現過載的operator(),傳入的引數個數可以用sizeof操作符得到,形如sizeof...(Args)。這個函式想要返回f(get<Nx>(t)...,rest...),想要得到這個序列可以通過一個輔助類和函式模板推導得到,程式碼比較直觀(可以通過檢視標準庫binder 的宣告,一步一步轉到定義檢視)

	template <typename T,T... Args>
	class Sequence {
	public:
		typedef Sequence<T, Args...> type;
		typedef T	value_type;
	};


Sequence類比較簡單,只有兩個typedef,很簡單一看就明白,這個類的作用就是通過為一個函式傳入一個Sequence 讓編譯器推匯出Args...序列。要想實現這個功能還需要一個輔助類,如下:


	template <bool b,
		size_t... args>
	class Make_Seq {

	};


	template< size_t n,size_t... Args>
	class Make_Seq<false,
		n,
		Args...> :public
		Make_Seq<n <= 1,
		n - 1,
		n,
		Args...>{

	};


	template<size_t... Args>
	class Make_Seq<true,
	0,
	Args...>:public
	Sequence<size_t,Args...>{

	};

這個類實現比較巧妙,這是一個繼承鏈,最頂層的基類是Sequence,主要實現部分是中間那部分程式碼,從程式碼可以看出,從繼承鏈的最底層往上,每增加一個繼承,Args...就會增加一個n,即當前繼承的層數。有了這樣兩個輔助類就可以讓編譯器推匯出引數,實現如下:

template <size_t... args>
void func(const Sequence& s) {

}

func(Make_Seq(false, 10));


例子中就會推匯出args... = 1,2...10

有了這個輔助類,就可以實現operater()了,首先要寫一個函式用來引數推導,這個函式應該直接返回仿函式的結果,因為想要將過載operater()中的不定引數傳入這個函式中,因為函式模板推導中不能有兩個不定引數推導,所以應當把operator()中的引數以一個tuple傳入函式中,這樣也面臨前面同樣的問題。我想到的方法是,將要binder的那些引數組成的tuple當作第一個引數和operator()需要的引數一起放到一個tuple內,並且為tulple的get函式寫一個過載,當傳入引數不是tulple時,直接返回傳入的引數,實現如下:


	template <int N, typename T>
	inline
		T
		get(T&& a) {
		return a;
	}

這個就是過載的get,傳入tuple的get和tuple實現有關在這就不討論了。下面說一下怎麼實現,首先在過載函式體內構造一個tuple:


tuple<tuple<Args...>, Rest...> t_arry(t, rest...);

第一個引數為一個tuple即binder的引數,後面是過載函式的引數。我們想要這樣實現,對於仿函式f,想要返回f(get<Ix>(get<Nx>(t_arry))...),其中比較重要的是Nx的實現,在沒有place holder的情況下,當Ix不大於binder的引數個數時,Nx為1,反之,Nx 等於 Ix 減去binder的引數個數,並且這個引數要在編譯期確定,藉助下面的模板實現:


template<bool C,int N1,int N2>
class NSwitch {

};

template< int N1, int N2>
class NSwitch<false,N1,N2> {
public:
	enum {N = N2};
};

template< int N1, int N2>
class NSwitch<true , N1, N2> {
public:
	enum { N = N1 };
};

 設繫結的引數個數為 n1,總共傳入仿函式的引數個數為n2,藉助上面的類實現如下:


NSwitch<n1 > Ix,1,0>::N * (Ix - n1)

binder完整實現如下:


template <typename Func, typename RET, typename... Args>
	class binder {
	public:
		binder(Func f1,Args... rest) :f(f1) ,t(rest...)) {
		}
		template <typename... Rest>
		RET operator ()(const Rest&... rest) {
			tuple<tuple<Args...>, Rest...> t_arry(t, rest...);
			return _Call<RET, sizeof...(Args)>(t, t_arry, Make_Seq<false, NSwitch<TestT<Args...>::N,sizeof...(Args),sizeof...(Args)+sizeof...(Rest)>::N>(), f);
		}
	private:
		Func f;
		tuple<Args...> t;
	};

_Call的實現如下:


	template <typename RET,
		int size,
		typename Func,
		typename	 T1,
		typename T2,
		size_t... Ix>
		RET _Call(T1& t1,
			T2& t2,
			Sequence<size_t, Ix...>& s,
			Func f) {
		return f(get<Ix>(get<1 + 
		 NSwitch<Ix > size,1,0>::N * (Ix -size)>(t2))...);
	}

這樣就實現了沒有place holder的binder,帶有place holder 的實現也類似,只需要加幾個輔助模板類,有空了再寫吧







相關文章