Search code examples
c++lambdaperfect-forwardingboost-hana

Perfect Forwarding for Lambda Chaining


I want to chain/compose multiple lambdas to a new lambda. When calling the new lambda with parameters it should call the original lambdas with these parameters. This lambda chaining does work well when the parameters are just copied but when I want to modify them perfect forwarding does not work:

#include <boost/hana.hpp>
#include <cassert>

constexpr auto chain_lambdas = [](auto... lambdas) {
    constexpr auto lambdasTpl = boost::hana::make_basic_tuple(lambdas...);

    return [lambdas(lambdasTpl)](auto&&... args) {
        auto argsTpl = boost::hana::make_basic_tuple(std::forward<decltype(args)>(args)...);

        boost::hana::for_each(lambdas, [args(argsTpl)](auto lambda) mutable {
            boost::hana::unpack(std::move(args), [lambda(lambda)](auto&&... args) {
                lambda(std::forward<decltype(args)>(args)...);
            });
        });
    };
};


auto main() -> int
{
    auto lambda1 = [](bool& called){
        called = true;    
    };

    auto lambda2 =  [](bool& called){
        called = true;    
    };

    bool called = false;
    chain_lambdas(lambda1, lambda2)(called);

    assert(called == true);
    return 0;
}

Code at the compiler explorer. Does perfect forwarding even work for this use case?


Solution

  • auto argsTpl = boost::hana::make_basic_tuple(std::forward<decltype(args)>(args)...); does copy (move) of arguments, so you only mutate the copy.

    You need std::forward_as_tuple equivalent for boost:

    constexpr auto chain_lambdas = [](auto... lambdas) {
        constexpr auto lambdasTpl = boost::hana::make_basic_tuple(lambdas...);
    
        return [lambdas(lambdasTpl)](auto&&... args) {
            auto argsTpl = boost::hana::basic_tuple<decltype(args)...>(std::forward<decltype(args)>(args)...);
    
            boost::hana::for_each(lambdas, [args(argsTpl)](auto lambda) mutable {
                boost::hana::unpack(args, [lambda(lambda)](auto&&... args) {
                    lambda(std::forward<decltype(args)>(args)...);
                });
            });
        };
    };
    

    Demo

    On my side, only with std (C++17), you might do:

    constexpr auto chain_lambdas = [](auto... lambdas) {
        return [=](auto&&... args) {
            return std::apply([&](auto... f){ (f(args...), ...); }, std::tie(lambdas...));
        };
    };
    

    Demo.

    I drop std::forward for args as calling function with moved object might be undesirable, but you can enable it if wanted.