7

Czy istnieje sposób na odzyskanie informacji o typie z lambda z domyślnymi parametrami przechowywanymi w funkcji std ::, która nie ma tych parametrów w swoim typie?Możliwość kopiowania std :: funkcji zawierającej lambdę z domyślnymi parametrami?

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 

Patrząc na kopię konstruktora std :: funkcyjnego, nie ma częściowej specjalizacji szablon dla innych typów funkcyjnych, tak bym sobie wyobrazić, ta informacja jest tracona i jest to tylko przypadek, że nie można przypisać funkcję jednego typu do funkcji innego typu, nawet jeśli wewnętrznie oba mogą wywoływać funkcję. Czy to jest poprawne? Czy istnieją jakieś rozwiązania, aby to osiągnąć? Patrzę na std :: function :: target, ale nie miałem szczęścia, nie jestem ekspertem od typów funkcji i wskaźników.

Na marginesie, w jaki sposób f1 (lub lambda) wiąże parametr domyślny?

+0

Jak zamierzasz wywołać f1 lub f2 (z wartością domyślną)? – nakiya

+1

@nakiya Jest to implementacja sygnałów i gniazd, która wymaga kopii gniazda (funkcji) podczas nawiązywania połączenia, sygnał wywołuje slot z parametrami przekazanymi z miejsca połączenia sygnału. Byłoby miło zezwolić na gniazda z domyślnymi parametrami, aby połączyć się z dowolnym sygnałem z sygnaturą, która jest w stanie go wywołać. –

Odpowiedz

4

Nie, to niemożliwe, ponieważ domyślne argumenty są własnością zestawu deklaracji funkcji, a nie samej funkcji. Innymi słowy, jest to całkowicie legalne 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) 

Wszystkie te mogą być ze sobą połączone w porządku.

Co oznacza, że ​​nie można w jakiś sposób "pobrać" domyślnego argumentu z funkcji.

Można zrobić odpowiednik f4 = f2 użyciu std::bind oraz zapewnienie własną domyślną argumentu, tak:

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

[Live example]

Jednakże, nie ma sposobu, aby dostać coś równoważnego do f3 = f1.

+0

"Możesz zrobić odpowiednik' f4 = f2' używając 'std :: bind'". Czy możesz podać przykład tego? – nakiya

+1

@nakiya coś takiego: '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)... 
    {} 
}; 

powyżej jest C++ 17 szkic surowego obiektu przechowywać więcej niż jedna krzywka operator().

Bardziej wydajna przechowuje obiekt tylko raz, ale przechowuje go w ten sposób na wiele sposobów. I pominąłem wiele szczegółów.

To naprawdę nie jest std::function, ale zgodny typ; funkcja STD przechowuje tylko jeden sposób wywoływania obiektu.

Oto "widok funkcji", który pobiera dowolną liczbę podpisów. Nie jest właścicielem obiektu, który należy nazwać.

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.

Używam C++ 17 using ..., ponieważ implementacja drzewa binarnego w C++ 14 jest brzydka.

Dla Państwa przypadku użycia, to looke jak:

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 

uwagę, że brak zarządzania dożywotnią w invoke_view oznacza, że ​​powyższe działa tylko gdy func_object nadal istnieje. (Jeśli wywołujemy widok wywołania w widoku wywołania, "wewnętrzny" widok wywołania jest również przechowywany przez wskaźnik, więc musi on nadal istnieć, a nie w przypadku, gdy przechowujemy widok wywołania w funkcji standardowej).

Dożywotnie zarządzanie celem, zrobione dobrze, wymaga trochę pracy. Chcesz użyć małej optymalizacji bufora za pomocą opcjonalnego inteligentnego wskaźnika lub czegoś, aby uzyskać rozsądną wydajność przy małych lambdach i uniknąć narzutów związanych z alokacją sterty.

Proste naiwne rozwiązanie przydzielające sterty zastąpiłoby void* z unique_ptr<void, void(*)(void*)> i przechowywanie w nim (lub podobnym) { new T(t), [](void* ptr){static_cast<T*>(ptr)->~T();} }.

To rozwiązanie powoduje, że obiekt funkcji porusza się tylko; sprawienie, by była ona kopiowalna, wymaga również wymazania operacji klonowania.