7

有沒有什麼辦法從lambda中恢復類型信息,其默認參數存儲在一個std :: function函數中,該函數的類型中沒有這些參數?可能用默認參數複製包含lambda的std :: function:

std::function<void()> f1 = [](int i = 0){}; 
std::function<void(int)> f2 = [](int i = 0){}; 
std::function<void(int)> f3 = f1; // error 
std::function<void()> f4 = f2;  // error 

看着的std ::功能的拷貝構造函數,還有其它功能類型的無模板偏特化,所以我想像這個信息將丟失,它只是你不能指定功能的情況下,的一種類型轉換爲另一種類型的功能,即使它們在內部它們都可以調用該功能。它是否正確?是否有任何解決方法來實現這一目標?我在尋找std :: function :: target,但沒有任何運氣,我不是專家的函數類型和指針。

在附註中,f1(或lambda)如何綁定默認參數?

+0

你打算如何調用f1或f2(使用默認值)? – nakiya

+1

@nakiya對於信號和插槽的實現,在連接時需要複製插槽(函數),信號使用從信號的調用位置傳遞的參數來調用插槽。如果允許具有默認參數的插槽連接到具有可以調用它的簽名的任何信號,那將會很不錯。 –

回答

4

不,這是不可能的,因爲默認參數是一組函數聲明的屬性,而不是函數本身的屬性。換句話說,這是完全合法的C++:

A.cpp

int f(int i = 42); 

const int j = f(); // will call f(42) 

B.cpp

int f(int i = 314); 

const int k = f(); // will call f(314) 

F.cpp

int f(int i = 0) 
{ 
    return i; 
} 

const int x = f(); // will call f(0) 

這些都可以連在一起就好了。

這意味着無法以某種方式從函數中「檢索」默認參數。

可以使用std::bind,並提供自己的默認參數,像這樣的f4 = f2相當於做:

std::function<void()> f4 = std::bind(f2, 42); 

[Live example]

但是,沒有辦法得到的東西相當於f3 = f1

+0

「你可以使用'std :: bind'」來做相當於'f4 = f2'的操作。你能提供一個這樣的例子嗎? – nakiya

+1

@nakiya像這樣:'std :: function f4 = std :: bind(f2,42)' –

1
template<class...Sigs> 
strucct functions:std::function<Sigs>...{ 
    using std::function<Sigs>::operator()...; 
    template<class T, 
    std::enable_if<!std::is_same<std::decay_t<T>,fundtions>{}>,int> =0 
    > 
    functions(T&&t): 
    std::function<Sigs>(t)... 
    {} 
}; 

上面是一個粗對象凸輪存儲多於一個operator()的C++ 17草圖。

一個效率更高的人只會存儲一次對象,但存儲如何以多種方式調用它。我跳過了很多細節。

它不是真的std::function,而是一個兼容的類型; std函數只存儲一種方法來調用對象。

這是一個「功能視圖」,可以使用任意數量的簽名。它不擁有待被稱爲的對象。

template<class Sig> 
struct pinvoke_t; 
template<class R, class...Args> 
struct pinvoke_t<R(Args...)> { 
    R(*pf)(void*, Args&&...) = 0; 
    R invoke(void* p, Args...args)const{ 
     return pf(p, std::forward<Args>(args)...); 
    } 
    template<class F, std::enable_if_t<!std::is_same<pinvoke_t, std::decay_t<F>>{}, int> =0> 
    pinvoke_t(F& f): 
     pf(+[](void* pf, Args&&...args)->R{ 
      return (*static_cast<F*>(pf))(std::forward<Args>(args)...); 
     }) 
    {} 
    pinvoke_t(pinvoke_t const&)=default; 
    pinvoke_t& operator=(pinvoke_t const&)=default; 
    pinvoke_t()=default; 
}; 

template<class...Sigs> 
struct invoke_view:pinvoke_t<Sigs>... 
{ 
    void* pv = 0; 
    explicit operator bool()const{ return pv; } 
    using pinvoke_t<Sigs>::invoke...; 
    template<class F, std::enable_if_t<!std::is_same<invoke_view, std::decay_t<F>>{}, int> =0> 
    invoke_view(F&& f): 
     pinvoke_t<Sigs>(f)... 
    {} 
    invoke_view()=default; 
    invoke_view(invoke_view const&)=default; 
    invoke_view& operator=(invoke_view const&)=default; 
    template<class...Args> 
    decltype(auto) operator()(Args&&...args)const{ 
     return invoke(pv, std::forward<Args>(args)...); 
    } 
}; 

Live example

我使用C++ 17 using ...,因爲C++ 14中的二叉樹實現是醜陋的。

爲您的使用情況下,它會像looke:

auto func_object = [](int i = 0){}; 
invoke_view<void(), void(int)> f1 = func_object; 
std::function<void(int)> f3 = f1; // works 
std::function<void()> f4 = f1;  // works 

注意,在invoke_view缺乏生命週期管理的意思是當func_object繼續存在以上纔有效。 (如果我們爲invoke視圖創建一個invoke視圖,那麼「inner」invoke視圖也由指針存儲,所以必須繼續存在;如果我們將invoke視圖存儲在一個std函數中,情況不會如此)。

目標的終身管理,做對了,需要一些工作。你會想要使用一個小的緩衝區優化和一個可選的智能指針或某些東西來獲得小lambda表達式的合理性能,並避免堆分配的開銷。

一個簡單樸素的始終堆分配解決方案將取代void*unique_ptr<void, void(*)(void*)>和存儲{ new T(t), [](void* ptr){static_cast<T*>(ptr)->~T();} }它(或類似)。

該解決方案使功能對象僅移動;使其可複製也需要鍵入擦除克隆操作。