We have polymorphic classes A and B like:
struct A {
virtual ~A() {}
};
struct B final : public A {
void f() { std::cout << "f" << std::endl; }
};
I want to assign a variable with std::function<void(A*)>
from the lambda function with the type of void(B*)
without explicitly applying dynamic_cast
for the arguments as
std::function<void(A*)> funcA = [](A* a) {
[](B* b) { b->f(); }(dynamic_cast<B*>(a));
};
B b;
funcA(&b);
Are there any ways to automatically achieve this without wrapping the internal function with [](A* a){}
?
I set off with the goal of making the following syntax work:
std::function<void(A*)> funcA = dynStdFunc([](B* b) { b->f(); });
To this end, dynStdFunc
must:
funcA
;dynamic_cast
.1. Detecting parameters has alrady been the subject of another answer of mine. We can use the following type trait:
// C++17's void_t
template <class...>
using void_t = void;
// Pack of arbitrary types
template <class...>
struct pack { };
namespace detail_parameters {
template <class F, class = void_t<>>
struct parameters { };
template <class F>
struct parameters<F, void_t<decltype(&F::operator ())>>
: parameters<decltype(&F::operator ())> { };
template <class R, class... Params>
struct parameters<R(Params...)> { using type = pack<Params...>; };
// More specializations for functions, function pointers,
// member function pointers...
}
// Retrieve the parameter list from a functionoid
template <class F>
using parameters = typename detail_parameters::parameters<std::remove_reference_t<F>>::type;
This takes in a functionoid type, and returns a pack<T...>
containing its parameter types. Great.
2. the parameters required by the std::function
aren't known from inside dynStdFunc
. The way we make this work is by returning a temporary object, which contains a template for a conversion operator to std::function<Ret(Args...)>
.
namespace detail_dynStdFunc {
// F = functionoid, Ps = pack of its parameters
template <class F, class Ps>
struct wrapper;
template <class F, class... Ps>
struct wrapper<F, pack<Ps...>> {
template <class Ret, class... Args>
operator std::function<Ret(Args...)> () {
// Now we know what parameters the `std::function` needs
}
F f;
};
}
template <class F>
auto dynStdFunc(F &&f) {
return detail_dynStdFunc::wrapper<
std::remove_reference_t<F>,
parameters<F>
>{std::forward<F>(f)};
}
3. We've got all we need, generating the new functor is straightforward:
template <class Ret, class... Args>
operator std::function<Ret(Args...)> () {
return [f_ = std::move(f)](Args... args) -> Ret {
return f_(dynamic_cast<Ps>(args)...);
};
}
And that's it! You can see it working live on Coliru.
Update: turns out I've done twice the work I needed to, because std::function
can actually instantiate and wrap generic functors directly. Thanks Yakk!
So performing the conversion ourselves is pointless -- let's drop the wrapper
:
template <class F, class... Ps>
auto dynStdFunc(F &&f, pack<Ps...>) {
return [f_ = std::forward<F>(f)](auto *... args) -> decltype(auto) {
return f_(dynamic_cast<Ps>(args)...);
};
}
template <class F>
auto dynStdFunc(F &&f) {
return dynStdFunc(std::forward<F>(f), parameters<F>{});
}
See it live on Coliru.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With