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.
Jak zamierzasz wywołać f1 lub f2 (z wartością domyślną)? – nakiya
@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ć. –