【问题标题】:c++ lambdas how to capture variadic parameter pack from the upper scopec ++ lambdas如何从上层范围捕获可变参数包
【发布时间】:2018-05-09 19:57:14
【问题描述】:

我研究了通用 lambda,并对示例稍作修改, 所以我的 lambda 应该捕获上层 lambda 的可变参数包。 所以基本上作为(auto&&...) 给上层lambda 的内容应该以某种方式在[=] 块中捕获。

(完美转发是另一个问题,我很好奇这里有可能吗?)

#include <iostream>
#include<type_traits>
#include<utility>


// base case
void doPrint(std::ostream& out) {}

template <typename T, typename... Args>
void doPrint(std::ostream& out, T && t, Args && ... args)
{
    out << t << " ";                // add comma here, see below
    doPrint(out, std::forward<Args&&>(args)...);
}

int main()
{
    // generic lambda, operator() is a template with one parameter
    auto vglambda = [](auto printer) {
        return [=](auto&&... ts) // generic lambda, ts is a parameter pack
        {
            printer(std::forward<decltype(ts)>(ts)...);
            return [=] {  // HOW TO capture the variadic ts to be accessible HERE ↓
                printer(std::forward<decltype(ts)>(ts)...); // ERROR: no matchin function call to forward
            }; // nullary lambda (takes no parameters)
        };
    };
    auto p = vglambda([](auto&&...vars) {
        doPrint(std::cout, std::forward<decltype(vars)>(vars)...);
    });
    auto q = p(1, 'a', 3.14,5); // outputs 1a3.14

    //q(); //use the returned lambda "printer"

}

【问题讨论】:

  • 旁白:你可能不想转发同一个包两次

标签: c++ lambda c++14 variadic-templates generic-lambda


【解决方案1】:

C++20 中的完美捕捉

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

C++17 和 C++14 解决方法

在 C++17 中,我们可以使用元组的解决方法:

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

不幸的是 std::apply 是 C++17,在 C++14 中你可以自己实现它或者用 boost::hana 做类似的事情:

namespace hana = boost::hana;

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

通过函数capture_call 简化解决方法可能很有用:

#include <tuple>

// Capture args and add them as additional arguments
template <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args){
    return [
        lambda = std::forward<Lambda>(lambda),
        capture_args = std::make_tuple(std::forward<Args>(args) ...)
    ](auto&& ... original_args)mutable{
        return std::apply([&lambda](auto&& ... args){
            lambda(std::forward<decltype(args)>(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))
        ));
    };
}

像这样使用它:

#include <iostream>

// returns a callable object without parameters
template <typename ... Args>
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>(args) ...);
}

// returns a callable object with two int parameters
template <typename ... Args>
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>(args) ...);
}

int main(){
    f1(1, 2, 3)();     // Call lambda without arguments
    f2(3, 4, 5)(1, 2); // Call lambda with 2 int arguments
}

这是capture_call的C++14实现:

#include <tuple>

// 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 <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args){
    return [
        lambda = std::forward<Lambda>(lambda),
        capture_args = std::make_tuple(std::forward<Args>(args) ...)
    ](auto&& ... original_args)mutable{
        return ::apply([&lambda](auto&& ... args){
            lambda(std::forward<decltype(args)>(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 按值捕获变量。完美意味着尽可能使用移动构造函数。下面是一个 C++17 代码示例,以便更好地理解:

#include <tuple>
#include <iostream>
#include <boost/type_index.hpp>


// Capture args and add them as additional arguments
template <typename Lambda, typename ... Args>
auto capture_call(Lambda&& lambda, Args&& ... args){
    return [
        lambda = std::forward<Lambda>(lambda),
        capture_args = std::make_tuple(std::forward<Args>(args) ...)
    ](auto&& ... original_args)mutable{
        return std::apply([&lambda](auto&& ... args){
            lambda(std::forward<decltype(args)>(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<decltype(b)>().pretty_name()
          << "\n";
    }();
    std::cout << "value capture end\n\n";

    [&b = a]{
        std::cout << "  type of the capture value: "
          << type_id_with_cvr<decltype(b)>().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<decltype(b)>().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<decltype(b)>().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<decltype(b)>().pretty_name()
          << "\n";
    }, std::move(a))();
    std::cout << "capture_call perfect capture end\n\n";
}

输出:

  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()

捕获值的类型在capture_call 版本中包含&amp;&amp;,因为我们必须通过引用访问内部元组中的值,而支持的语言捕获支持直接访问该值。

【讨论】:

  • 我对 C++17 解决方案(使用 std::make_tuple() 和 std::apply() 的解决方案)有点困惑。首先,我希望 make_tuple() 创建的元组包含 std::decay-ed 类型,除了 std::reference_wrapper 类型的参数。此外,std::apply() 会将元组转发给 std::get(),如果我们将左值引用传递给元组,我相信它将返回左值引用,否则将返回右值引用。所以总而言之,看起来我们并没有完美地转发捕获中的参数包。我错过了什么?
  • @fireboot 捕获的对象作为值存储在 lambda 对象中。 “完美”捕获是指通过完美转发完成这些值的构建。 apply 将始终为您提供对捕获的(在存储的元组中)值的右值引用,这相当于定义了可变的 C++20 版本 lambda。我添加了一个示例以便更好地理解。多亏了你,我在原始的 capture_call 中发现了一个错误!之前的第二个 make_tuple 现在被第二个 apply 替换,它为捕获的值创建一个右值引用“tuple-view”。
  • 为什么是 make_tuple,而不是 forward_as_tuple?
  • C++20 解决方案不是也需要mutable 吗? Args&amp;&amp; args 应该是 Args&amp;&amp; ...args
  • @Mr.Wonko 你对... 的看法是正确的,我已修复它。谢谢!是否需要mutable 取决于闭包中的值是否应为const。所以是的,如果你想要旧解决方法的确切行为,那么是的。
【解决方案2】:

您可以使用std::bind 将可变参数绑定到您的 lambda(对于 C++20 之前的解决方案),而不是使用 std::tuplestd::apply,这会使代码变得很混乱:

template <typename... Args>
auto f(Args&&... args){

    auto functional = [](auto&&... args) { /* lambda body */ };
    return std::bind(std::move(functional), std::forward<Args>(args)...);
}

【讨论】:

    【解决方案3】:

    完美转发是另一个问题,我很好奇这里有可能吗?

    嗯...在我看来,完美的转发的问题。

    ts... 的捕获效果很好,如果您在内部 lambda 中进行更改,

    printer(std::forward<decltype(ts)>(ts)...);
    

    printer(ts...);
    

    程序编译。

    问题是通过捕获ts...(使用[=])它们变成const值和printer()(即接收auto&amp;&amp;...vars的lambda)接收引用( &amp;&amp;&amp;)。

    你可以用下面的函数看到同样的问题

    void bar (int &&)
     { }
    
    void foo (int const & i)
     { bar(std::forward<decltype(i)>(i)); }
    

    从 clang++ 我得到

    tmp_003-14,gcc,clang.cpp:21:4: error: no matching function for call to 'bar'
     { bar(std::forward<decltype(i)>(i)); }
       ^~~
    tmp_003-14,gcc,clang.cpp:17:6: note: candidate function not viable: 1st argument
          ('const int') would lose const qualifier
    void bar (int &&)
         ^
    

    解决问题的另一种方法是将ts... 捕获为引用(因此[&amp;])而不是值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-22
      • 2020-02-13
      相关资源
      最近更新 更多