如何正确地将参数包一般地转发到 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
的引用,还有对 int
和 float
.
的引用
一个可能的解决方案如下:
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
将其组件传递给被调用的实际函数。
我正在尝试从基函数转发通用参数包,但这样做很麻烦,尤其是在类型列表中存在非文字非引用类型的情况下
考虑以下示例:
#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
的引用,还有对 int
和 float
.
一个可能的解决方案如下:
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
将其组件传递给被调用的实际函数。