Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatic dynamic_cast for arguments in std::function

Tags:

c++

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){}?

like image 548
akarin64 Avatar asked Sep 19 '25 04:09

akarin64


1 Answers

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:

  1. Detect the parameters of the provided lambda;
  2. Detect the parameters of funcA;
    This isn't actually needed on our side, see the update at the end.
  3. Generate a new functor, which glues both parameter lists together via 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.

like image 194
Quentin Avatar answered Sep 21 '25 19:09

Quentin