C++標准 bind函數用法與C#簡單實現
在看C++標准程序庫書中,看到bind1st,bind2nd及bind的用法,當時就有一種熟悉感,仔細想了下,是F#裡提到的柯裡化。下面是維基百科的解釋:在計算機科學中,柯裡化(英語:Currying),又譯為卡瑞化或加裡化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受余下的參數而且返回結果的新函數的技術。
下面來看一個簡單的例子。
void mult(int& a, int b)
{
cout << "a:" << a << " b:" << b << endl;
a += b;
}
void test24()
{
using namespace std::placeholders;
vector<int> list;
int i = 0;
generate_n(back_inserter(list), 10, [&i](){
return i++;
});
for_each(list.begin(), list.end(), bind(mult, _1, 10));
for_each(list.begin(), list.end(), bind(mult, 100, _1));
copy(list.begin(), list.end(), ostream_iterator<int>(cout, " "));
}
在這,for_each最後接受一個void fun(int p)的函數做參數,p就是我們的每次遍歷的數據,而在這我們用到mult,帶有二個參數。在這我們就要用到柯裡化,把mult轉成前面的void fun(int p)的形式,下面我們看下相應函數如何工作。
我們先來看下bind1st,這個相當於做了如下事。 f(a,b) -> f(a)(b).簡單來說,就是把帶二個參數的函數變成只帶一個參數的函數的過程。bind2nd如上,類似f(a,b)->f(b)(a).而bind的用法更廣,不限制個數,參數順序和函數類型等。上面第一個for_each中bind的用法就相當於bind2nd,第二個就相當於bind1st.
下面再來看個小例子:
void test23()
{
using namespace std::placeholders;
auto func = [](int x, string y){
return to_string(x) + y;
};
auto f = bind(func, 1, _1);
auto fs = bind(func, _1, "xx");
auto fss = bind(func, 3, "xxx");
auto fsss = bind(func, _2, _1);
cout << f("x") << endl;
cout << fs(2) << endl;
cout << fss() << endl;
cout << fsss("xxxx", 4) << endl;
}
輸出結果分別是1x,2xx,3xxx,4xxxx.在二個參數的情況下,bind的幾種簡單重組函數的方法。為了好理解與說明,我直接把對應F#裡相應寫法寫出。
let func x y = x.ToString() + y
let f x = func 1 x
let fs x = func x "xx"
let fss = func 3 "xxx"
let fsss x y = func y x
[<EntryPoint>]
let main argv =
printfn "%s" (f "x")
printfn "%s" (fs 2)
printfn "%s" (fss)
printfn "%s" (fsss "xxxx" 4)
ignore(System.Console.Read())
0 // 返回整數退出代碼
F#因為本身就是FP語言,故相應在C++還需要調用外部函數相比,本身內部支持。
如下是對應各變量類型:
val func : x:'a -> y:string -> string
val f : x:string -> string
val fs : x:'a -> string
val fss : string = "3xxx"
val fsss : x:string -> y:'a -> string
在這,我們把泛形a具體化成int類型,好做說明,func (int,string)->string.而f是func第一參數具體化後生成的新的函數,fs是第二個參數具體化後生成新的函數,其中fss略過,而fsss則是把原(int,string)->string類型函數變成(string,int)->string的類型函數。
如果F#難理解,下面是C#版的bind方法,只是簡單針對二個參數的函數情況下,希望這個有助大家理解。
public class BindHelper
{
public static Func<T1, T> bind<T1, T2, T>(Func<T1, T2, T> fun, T2 t2)
{
return (t11) =>
{
return fun(t11, t2);
};
}
public static Func<T2, T> bind<T1, T2, T>(Func<T1, T2, T> fun, T1 t1)
{
return (t22) =>
{
return fun(t1, t22);
};
}
public static Func<T> bind<T1, T2, T>(Func<T1, T2, T> fun, T1 t1, T2 t2)
{
return () =>
{
return fun(t1, t2);
};
}
public static Func<T2, T1, T> bind<T1, T2, T>(Func<T1, T2, T> fun)
{
return (t22, t11) =>
{
return fun(t11, t22);
};
}
static void Main()
{
Func<int, string, string> func = (int x, string y) => { return x.ToString() + y; };
var f = bind(func, 1);
var fs = bind(func, "xx");
var fss = bind(func, 3, "xxx");
var fsss = bind(func);
Console.WriteLine(f("x"));
Console.WriteLine(fs(2));
Console.WriteLine(fss());
Console.WriteLine(fsss("xxxx", 4));
Console.Read();
}
}
這個應該是最好理解了,相應bind的重載方法在C#中列出如何實現。
最後上文中float(*(*f)(float, float))(float)如何初始化還是沒搞定,不過相應類似的可以正確初始化。也可以看下bind中帶bind代表的方法與意義。
//如何具體化.
float(*(*f)(float, float))(float);
auto fvv = function<function<float(float)>(float, float)>(f);
auto fv = [](float f, float d){
return[](float c)
{
return c;
};
};
using namespace std::placeholders;
fvv = fv;
//f = fv;
auto x = bind(bind(fv, _1, _1)(4), _1)(6);
auto xxx = fv(3, 4)(2.0f);
auto yyy = fvv(3, 4)(2.0f);
cout << x << endl;
PS:STL剛開始看,只能說C++的模板與泛形太強大了,相應模板方法可以用動態語言的方式寫(聲明有這元素,這元素能做啥,就和javascript一樣),而編譯時,根據調用相應模板方法得到正確的實例方法就相當於運行結果。所以很多模板調用錯誤是在編譯階段指出來的。