c++ lambdas how to capture variadic parameter pack from the upper scope

后端 未结 3 512
梦如初夏
梦如初夏 2020-12-12 20:43

I study the generic lambdas, and slightly modified the example, so my lambda should capture the upper lambda\'s variadic parameter pack. So basically what is given to upper

3条回答
  •  [愿得一人]
    2020-12-12 20:59

    Perfect capture in C++20

    template 
    auto f(Args&& ... args){
        return [... args = std::forward(args)]{
            // use args
        };
    }
    

    C++17 and C++14 workaround

    In C++17 we can use a workaround with tuples:

    template 
    auto f(Args&& ... args){
        return [args = std::make_tuple(std::forward(args) ...)]()mutable{
            return std::apply([](auto&& ... args){
                // use args
            }, std::move(args));
        };
    }
    

    Unfortunately std::apply is C++17, in C++14 you can implement it yourself or do something similar with boost::hana:

    namespace hana = boost::hana;
    
    template 
    auto f(Args&& ... args){
        return [args = hana::make_tuple(std::forward(args) ...)]()mutable{
            return hana::unpack(std::move(args), [](auto&& ... args){
                // use args
            });
        };
    }
    

    It might be usefull to simplify the workaround by a function capture_call:

    #include 
    
    // Capture args and add them as additional arguments
    template 
    auto capture_call(Lambda&& lambda, Args&& ... args){
        return [
            lambda = std::forward(lambda),
            capture_args = std::make_tuple(std::forward(args) ...)
        ](auto&& ... original_args)mutable{
            return std::apply([&lambda](auto&& ... args){
                lambda(std::forward(args) ...);
            }, std::tuple_cat(
                std::forward_as_tuple(original_args ...),
                std::apply([](auto&& ... args){
                    return std::forward_as_tuple< Args ... >(
                        std::move(args) ...);
                }, std::move(capture_args))
            ));
        };
    }
    

    Use it like this:

    #include 
    
    // returns a callable object without parameters
    template 
    auto f1(Args&& ... args){
        return capture_call([](auto&& ... args){
            // args are perfect captured here
            // print captured args via C++17 fold expression
            (std::cout << ... << args) << '\n';
        }, std::forward(args) ...);
    }
    
    // returns a callable object with two int parameters
    template 
    auto f2(Args&& ... args){
        return capture_call([](int param1, int param2, auto&& ... args){
            // args are perfect captured here
            std::cout << param1 << param2;
            (std::cout << ... << args) << '\n';
        }, std::forward(args) ...);
    }
    
    int main(){
        f1(1, 2, 3)();     // Call lambda without arguments
        f2(3, 4, 5)(1, 2); // Call lambda with 2 int arguments
    }
    

    Here is a C++14 implementation of capture_call:

    #include 
    
    // Implementation detail of a simplified std::apply from C++17
    template < typename F, typename Tuple, std::size_t ... I >
    constexpr decltype(auto)
    apply_impl(F&& f, Tuple&& t, std::index_sequence< I ... >){
        return static_cast< F&& >(f)(std::get< I >(static_cast< Tuple&& >(t)) ...);
    }
    
    // Implementation of a simplified std::apply from C++17
    template < typename F, typename Tuple >
    constexpr decltype(auto) apply(F&& f, Tuple&& t){
        return apply_impl(
            static_cast< F&& >(f), static_cast< Tuple&& >(t),
            std::make_index_sequence< std::tuple_size<
                std::remove_reference_t< Tuple > >::value >{});
    }
    
    // Capture args and add them as additional arguments
    template 
    auto capture_call(Lambda&& lambda, Args&& ... args){
        return [
            lambda = std::forward(lambda),
            capture_args = std::make_tuple(std::forward(args) ...)
        ](auto&& ... original_args)mutable{
            return ::apply([&lambda](auto&& ... args){
                lambda(std::forward(args) ...);
            }, std::tuple_cat(
                std::forward_as_tuple(original_args ...),
                ::apply([](auto&& ... args){
                    return std::forward_as_tuple< Args ... >(
                        std::move(args) ...);
                }, std::move(capture_args))
            ));
        };
    }
    

    capture_call captures variables by value. The perfect means that the move constructor is used if possible. Here is a C++17 code example for better understanding:

    #include 
    #include 
    #include 
    
    
    // Capture args and add them as additional arguments
    template 
    auto capture_call(Lambda&& lambda, Args&& ... args){
        return [
            lambda = std::forward(lambda),
            capture_args = std::make_tuple(std::forward(args) ...)
        ](auto&& ... original_args)mutable{
            return std::apply([&lambda](auto&& ... args){
                lambda(std::forward(args) ...);
            }, std::tuple_cat(
                std::forward_as_tuple(original_args ...),
                std::apply([](auto&& ... args){
                    return std::forward_as_tuple< Args ... >(
                        std::move(args) ...);
                }, std::move(capture_args))
            ));
        };
    }
    
    struct A{
        A(){
            std::cout << "  A::A()\n";
        }
    
        A(A const&){
            std::cout << "  A::A(A const&)\n";
        }
    
        A(A&&){
            std::cout << "  A::A(A&&)\n";
        }
    
        ~A(){
            std::cout << "  A::~A()\n";
        }
    };
    
    int main(){
        using boost::typeindex::type_id_with_cvr;
    
        A a;
        std::cout << "create object end\n\n";
    
        [b = a]{
            std::cout << "  type of the capture value: "
              << type_id_with_cvr().pretty_name()
              << "\n";
        }();
        std::cout << "value capture end\n\n";
    
        [&b = a]{
            std::cout << "  type of the capture value: "
              << type_id_with_cvr().pretty_name()
              << "\n";
        }();
        std::cout << "reference capture end\n\n";
    
        [b = std::move(a)]{
            std::cout << "  type of the capture value: "
              << type_id_with_cvr().pretty_name()
              << "\n";
        }();
        std::cout << "perfect capture end\n\n";
    
        [b = std::move(a)]()mutable{
            std::cout << "  type of the capture value: "
              << type_id_with_cvr().pretty_name()
              << "\n";
        }();
        std::cout << "perfect capture mutable lambda end\n\n";
    
        capture_call([](auto&& b){
            std::cout << "  type of the capture value: "
              << type_id_with_cvr().pretty_name()
              << "\n";
        }, std::move(a))();
        std::cout << "capture_call perfect capture end\n\n";
    }
    

    Output:

      A::A()
    create object end
    
      A::A(A const&)
      type of the capture value: A const
      A::~A()
    value capture end
    
      type of the capture value: A&
    reference capture end
    
      A::A(A&&)
      type of the capture value: A const
      A::~A()
    perfect capture end
    
      A::A(A&&)
      type of the capture value: A
      A::~A()
    perfect capture mutable lambda end
    
      A::A(A&&)
      type of the capture value: A&&
      A::~A()
    capture_call perfect capture end
    
      A::~A()
    

    The type of the capture value contains && in the capture_call version because we have to access the value in the internal tuple via reference, while a language supported capture supports direct access to the value.

提交回复
热议问题