lefticus / cpp_weekly

The official C++ Weekly Repository. Code samples and notes of future / past episodes will land here at various times. PR's will be accepted in some cases.
The Unlicense
688 stars 26 forks source link

Moving parameter packs into a lambda, is it possible? #398

Closed espenkn closed 1 month ago

espenkn commented 1 month ago

Channel "C++Weekly" episode request

Topics

Moving parameter packs into a lambda, is it possible?

Length

Probably short, comparable to the episode about Transforming Lambdas. I saw the trick using 'initialized lambda pack capture' or 'structured packing' or whatever the correct term is and want to do something like that with move.

So I know that I can move a template argument into a lambda, but I want a template that takes an parameter pack and moves it like:

Context for the example given here is for working with Java JNI and we want to create this function somewhere and perhaps even execute it on another thread so we can not have any dangling references and the wrapper types contains smart pointer and i would like to use an unique pointer. Is there a way to otherwise make this code so that we know we would have extended the lifetime so that we don't have any invalid references?

template <typename Callable, typename... JavaClassesWrapper>
    [[nodiscard]] static auto CreateRunnable(Callable&& func, JavaClassesWrapper&& ... wrappers)
    {
        return [func = std::move(func),  ...wrappers = std::move(wrappers)] (JNIEnv* env) mutable {
            func(env, wrappers.GetObjectFromWeak(env)...);
            // Cleanup wrappers
            (wrappers.Cleanup(env), ...);
        };
    }

I can not get this to work (with my compiler) since it still insists on a copy. I have a working variant that's using copies with mutable types to emulate a move, but i really want the proper moves...

Hope you consider this since I can not figure out if there is a way to move a pack into lambdas and that would be neat.

LB-- commented 1 month ago

Typically you would use std::forward instead of std::move when you use && with a template parameter due to reference collapsing rules, but otherwise I don't see why this isn't working for you. Are you sure the issue isn't with where you're storing the lambda? For example, std::function always requires the functor to be copyable even if it is never copied, consider upgrading to std::move_only_function instead.

espenkn commented 1 month ago

Typically you would use std::forward instead of std::move when you use && with a template parameter due to reference collapsing rules, but otherwise I don't see why this isn't working for you. Are you sure the issue isn't with where you're storing the lambda? For example, std::function always requires the functor to be copyable even if it is never copied, consider upgrading to std::move_only_function instead.

OMG, I think you are correct that it is passing the lambda that is the issue! Thank you so much for the reply, I now just need to find a suitable alternative for std::move_only_function since i don't have it in my compiler. I did not even know about the copy requirement on std::function and I have not heard about the new move_only_function. Thanks for enlightening me.

As for the episode idea, perhaps a show and tell of std::move_only_function is what i wanted instead, with also alternatives for us that don't have c++23 yet.

LB-- commented 1 month ago

In a pinch you can wrap the lambda into a functor that is copyable, for example with std::shared_ptr - not ideal but it works:

template<typename Lambda>
class shared_lambda
{
    std::shared_ptr<Lambda> lambda;
public:
    shared_lambda(Lambda&& lambda_) : lambda(std::make_shared<Lambda>(std::move(lambda_))) {}
    decltype(auto) operator()(auto&&... args) noexcept(std::is_nothrow_invocable_v<Lambda, decltype(args)...>)
    {
        return std::invoke(*lambda, std::forward<decltype(args)>(args)...);
    }
};

Demo: https://gcc.godbolt.org/z/8xT4vfKhf

espenkn commented 1 month ago

Thanks again @LB-- for your excellent help, it so much more appreciated than i can put into words.

I ended up in the end shimming this with a slightly modified version of this idea here https://stackoverflow.com/a/52358928/2642865 until I can get proper std-23, hopefully withing the near future.

To be honest that shim don't sit right with me, so I might drop it and just go for your wrapper instead :heart:

lefticus commented 1 month ago

This comment thread plus #329 - I'm closing this issue I think. Create a new one if there's something else you want to discuss.