C++ 如何自動推導遞歸 lambda 函數的類型?
c++用的不熟, 今天因為希望一坨某類的private函數只對該類的某一個public函數可見, 就想索性把這一坨private函數都取消然後定義成該public函數內部的私有函數... 於是大抵寫出了這樣的代碼:
雖然我來問這個問題的時間已經夠我寫好幾遍函數類型了...還是是可以實現上面的寫法只是我不知道呢? 還是c++就是沒法推導出匿名auto函數的類型?
#include&
#include&
using std::function;
int main()
{
/* function&fac = [](int x)-&>int{ */
auto fac = [](int x)-&>int{
if(x &< 1) return 1; else return x * fac(x-1); }; std::cout&<&無法自動推導出fac的函數類型...手動寫函數類型, 就能夠正常.
PS: 這裡並不是在問如何在匿名函數中用Y combinator實現遞歸, 只是想問為何auto類型無法推導...因為在haskell中, 下面的函數是很輕易的被推導出類型的
fac = x-&>
if(x &< 1) then 1 else x * (fac $ x-1) -- :t fac fac :: Integer-&>Integer
在這裡, C++之無法成功推導函數類型的原因是什麼?
這個可能常規方法做不到,抑或是我也不知道。但是,看到這個問題,我想到了兩個解決方案。首先是,傳遞一個泛型的右值引用參數,作為自身函數的入參,如下列代碼所示:
// clang++ 3.5
// maybe gcc 4.9 support it, but I don"t test it
#include&
int main()
{
auto fac = [](auto self, int x)-&>int{
return x &< 1 ? 1 : x * self(self, x - 1);
};
std::cout&<&
// clang++ 3.5
// maybe gcc 4.9 support it, but I don"t test it
#include&
template&
struct wrapper_type
{
Functor functor;
template&
decltype(auto) operator()(Args... args) const
{
return functor(functor, std::forward&
}
};
template&
wrapper_type&
{
return{ std::forward&
}
int main()
{
auto fac = wrapper([](auto self, int x)-&>int{
return x &< 1 ? 1 : x * self(self, x - 1);
});
std::cout &<&< fac(3) &<&< std::endl; //6
return 0;
}
C++ 的 auto 並不基於 Unification 所以做不到Unification 推理的意思是啥呢?遇到一個新變數,我可以先假設它的類型是 X,然後把未知數 X 弄到後面的推理過程裡面來,這樣會得到一些關於 X 的方程。我們最後解出這個方程(這過程就叫做 Unification),就可以算出 X 是什麼了。
vczh_toys/Main.cpp at master · vczh/vczh_toys · GitHub
我這個雖然不能自動推導,但是解決了你的lambda傳出函數之後調用會AV的問題。要完全解決這個問題只能等C++14,可以寫模板lambda函數之後,才能完全不要那個函數類型。
使用Lambda表達式編寫遞歸函數
@趙劼
基本上就是本文的C#版。根子上和 @bitdewy 一樣,就是靠fixed point做。
對於 @藍色 的答案,有一個解釋其原理的版本,不需要C++14.
#include &
struct foo
{
static int exec(int x)
{
return foo()(foo(), x);
}
template &
int operator ()(T func, int x)
{
std::cout &<&< __PRETTY_FUNCTION__ &<&< std::endl;
if(x &> 1) return x * func(func, x-1);
return 1;
}
};
int main(int argc, char** argv)
{
std::cout &<&< "Result: " &<&< foo::exec(5) &<&< std::endl;
return 0;
}
自問自答... 仍然不知道怎麼讓c++推導auto遞歸函數, 不過測試過程中發現一些很有意思的事,
有些函數Haskell推導不出, C++推導的出, 有些函數c++推導不出, Haskell推導的出...
就是用階乘函數為例...c++的大抵需要g++到4.9.2...C++所以這裡的問題又來了, g++認為fac函數以及self的類型是什麼? 因為同樣的代碼直接翻譯成haskell, 是會因為infinite loop而無法推導出類型的...#include&
#include&
using std::function;
int main()
{
//tested at g++ 4.9.2, 好像版本低一點也編譯不過
//g++ -std=c++1y -o anony anony.cpp
//c++推導的出類型
auto fac = [](auto self,auto n)-&>auto{
if(n &< 1)
return 1;
else
return n * self(self,n-1);
};
//fail to compile for this function
//c++推導不出類型
auto fac2 = [](int n)-&>int{
if(n &< 1)
return 1;
else
return n * fac2(n-1);
};
std::cout&<&
-- haskell推導的出類型
fac1 =
-&>
if(n &< 1)
then 1
else n * (fac1 $ n-1)
-- haskell推導不出類型
fac2 self 0 = 1
fac2 self n = n * (self self $ n-1)
data Y a = Y ((Y a)-&>a-&>a)
fac2 self 0 = 1
fac2 self n = n * ((applY self self) (n-1))
where applY (Y f1) f2 = f1 f2
fact2 = fac2 $ Y fac2
我來說一下自己的理解。。。首先,我們要知道,fac的類型並不是function&
auto聲明變數的時候其類型由初始化表達式類型確定。對於lambda來說整個函數體就是初始化表達式,在中間遞歸調用自身的時候其類型無法推定,必須使用function提前指定類型才可以。
這個問題沒有清爽的解決方案....匿名lambda遞歸需要用C++的Y Combinator,參考vczh_toy裡面的源碼,主要編譯器沒提供這個語法糖...不然例如提供個self關鍵詞在lambda內使用,就方便多了.==================================================================這裡並不是在問如何在匿名函數中用Y combinator實現遞歸, 只是想問為何auto類型無法推導...額.個人看法是:這個auto fac=lambda.fac的類型是來源於lambda.編譯器應該編譯完lambda才會給fac指定類型,lambda編譯的時候用到了fac.fac的類型是lambda編譯完才指定的,自然這裡沒法推導fac類型
std::function fac = ...
lambda可以自動轉換為函數指針。
用函數指針的匹配應該可以。(不再電腦邊上,先mark下)#include &
#include &
#define lambda(NAME, CAP, PROTO, BODY)
[]() {
std::function& (NAME) = CAP PROTO {BODY};
return (NAME);
}()
int main() {
auto fac = lambda(fac, [], (int x) -&> int, {
if (x &< 1) {
return 1;
} else {
return x * fac(x - 1);
}
});
std::cout &<&< fac(3) &<&< std::endl;
return 0;
}
auto 變數的初始化表達式中不能出現 auto 變數本身,正面硬上是無解的。
於是耍個賴,結了。ps. C++的 lambda 既然號稱函數對象的語法糖,在函數體內連個 this 都沒有,也是蠻拼的。推薦閱讀:
※關於c++ default constructor的問題,這個說法對嗎?
※c++中的左值跟右值怎麼區分?
※語句str2= str1 + (str1.size() - 1," ")為什麼只有1個空格添加進去了?
※C++中類B需要訪問類A的私有成員變數,除了將B聲明為A的友元類外還有其它方法嗎?
※如何在#define里使用"#"?