標籤:

C++ 如何自動推導遞歸 lambda 函數的類型?

c++用的不熟, 今天因為希望一坨某類的private函數只對該類的某一個public函數可見, 就想索性把這一坨private函數都取消然後定義成該public函數內部的私有函數... 於是大抵寫出了這樣的代碼:

#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的函數類型...手動寫函數類型, 就能夠正常.

雖然我來問這個問題的時間已經夠我寫好幾遍函數類型了...還是是可以實現上面的寫法只是我不知道呢? 還是c++就是沒法推導出匿名auto函數的類型?

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&<&

當然,這可能不太友好,如果需要達到題主的fac(3)的調用,我們需要再次想要一個辦法進行修正,如下列代碼所示:

// 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&(args)...);
}
};

template&
wrapper_type&::type&> wrapper(Functor functor)
{
return{ std::forward&(functor) };
}

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; }

如果題主有興趣,倒是可以仔細看看我第二種方案的實現,倒是蠻有趣的。Enjoy it! 謝邀 :-)


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++

#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&<&

所以這裡的問題又來了, g++認為fac函數以及self的類型是什麼? 因為同樣的代碼直接翻譯成haskell, 是會因為infinite loop而無法推導出類型的...

Haskell:

-- 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)

為了讓上面推導不出類型的Haskell成功跑起來, 寫出了如下醜陋的代碼:

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&。。。

fac是lambda類型,裡面會有一個閉包。。。

而這個閉包裡面究竟包含哪些東西呢?需要編譯器編譯lambda函數裡面的部分才能知道。。。

所以,fac的類型必須在編譯器編譯好整個lambda函數之後才能確定。。。

所以你要先編譯lambda那部分代碼,然後發現wtf,fac是個啥類型根本沒辦法確定!

於是編譯器就傲嬌了。。。

所以本質上的原因是C++實現中lambda函數的類型都是完全不同的,並不是fuction&,所以在看到[](int)-&>int時無法對類型進行推導,只能依靠後面依賴了fac的部分進行推導,導致循環依賴。。。


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里使用"#"?

TAG:C | 函數式編程 |