如何正确地将参数包一般地转发到 lambda 中?

How to properly generically forward a parameter pack into a lambda?

我正在尝试从基函数转发通用参数包,但这样做很麻烦,尤其是在类型列表中存在非文字非引用类型的情况下

考虑以下示例:

#include <utility>
#include <iostream>
#include <future>
#include <vector>

template < typename... Args >
class BaseTemplate {
 public:
    BaseTemplate() = default;
    virtual ~BaseTemplate() = default;

    virtual std::future< void > call(Args... args) {
        return std::async(std::launch::async, [this, &args... ] {
            // ..
            first(std::forward<Args>(args)...);
            second(std::forward<Args>(args)...);
            third(std::forward<Args>(args)...);
            // ...
        });
    }

 protected:
    virtual void first(Args...) { /* ... */ }
    virtual void second(Args...) = 0;
    virtual void third(Args...) { /* ... */ }
};


class SomeType {
 public:
    explicit SomeType(std::vector< float >* data) : ptr(data) { /* ... */ }
    ~SomeType() = default;
    // ...
    // protected:
    float member = 5.6;
    std::vector< float >* ptr;
};


class Derived1 : public BaseTemplate< int, float, SomeType > {
 public:
    using Base = BaseTemplate< int, float, SomeType >;

    Derived1() : Base() { /* ... */ }
    ~Derived1() = default;

 protected:
    void second(int, float, SomeType obj) override {
        std::cout << "Derived1::" << __func__ << " (" << obj.member << ")" << std::endl;
        printf("%p\n", obj.ptr);
        for (const auto& val : *(obj.ptr)) {
            std::cout << val << std::endl;
        }
    }
};


class Derived2 : public BaseTemplate< int, float, const SomeType& > {
 public:
    using Base = BaseTemplate< int, float, const SomeType& >;

    Derived2() : Base() { /* ... */ }
    ~Derived2() = default;

 protected:
    void second(int, float, const SomeType& obj) override {
        std::cout << "Derived2::" << __func__ << " (" << obj.member << ")" << std::endl;
        printf("%p\n", obj.ptr);
        for (const auto& val : *(obj.ptr)) {
            std::cout << val << std::endl;
        }
    }
};



int main(int argc, char const *argv[]) {
    std::vector< float > data {0, 1, 2, 3};

    SomeType obj(&data);
    Derived1 foo1;
    Derived2 foo2;

    // auto bar1 = foo1.call(1, 5.6, obj);  // Segmentation fault
    auto bar2 = foo2.call(1, 5.6, obj);  // OK

    // ...

    // bar1.wait();
    bar2.wait();

    return 0;
}    

如果 SomeType 通过引用传递,一切都按预期工作,但如果通过值传递,则 segfaults。为了解决这两种情况,使用 std::forward<>() 的正确方法是什么?

问题不在于 std::forward 个调用;该程序在到达它们之前表现出未定义的行为。 call 是按值取一些参数,但是里面的lambda 总是按引用来捕获的。因此,它最终持有对局部变量的引用,这些变量在 call returns 时被销毁 - 但稍后调用 lambda,可能在不同的线程上。那时,所有这些引用都是悬而未决的——不仅是对 SomeType 的引用,还有对 intfloat.

的引用

一个可能的解决方案如下:

virtual std::future< void > call(Args... args) {
    std::tuple<BaseTemplate*, Args...> t{this, args...};
    return std::async(std::launch::async, [t] {
        // ..
        std::apply(&BaseTemplate::first, t);
        std::apply(&BaseTemplate::second, t);
        std::apply(&BaseTemplate::third, t);
        // ...
    });
}

我们将参数存储在一个元组中——按值传递的参数的副本,对按引用传递的参数的引用。然后 lambda 按值捕获此元组,并使用 std::apply 将其组件传递给被调用的实际函数。

Demo