在C20/C23中,将参数完美地转发到lambda捕获中的最干净的方法是什么?这里我指的是在协程对象内部通过复制捕获右值,通过引用捕获左值:
struct A { int _value{0}; };
auto foo = []<typename T>(T&& a) {
return [a = std::forward<T>(a)]() mutable {
++a._value;
std::cout << a._value << "\n";
};
};
A my_a;
auto capture_as_lvalue = foo(my_a);
capture_as_lvalue(); // Prints `1`.
capture_as_lvalue(); // Prints `2`.
capture_as_lvalue(); // Prints `3`.
std::cout << my_a._value << "\n"; // Should print `3`.
auto capture_as_rvalue = foo(A{});
capture_as_rvalue(); // Prints `1`.
This answer似乎表明上述程序应该可以工作,但上面的程序(https://godbolt.org/z/Mz3caah5o)导致
1
2
3
0 <- should be 3
1
Vittorio罗密欧的A blog post使用宏来实现所需的效果。一个缺点是捕获使用指针语义,而不是引用的隐式语义。In this answer Fabio A.建议使用推导向导的更简单的方法:
// This is the case when just one variable is being captured.
template <typename T>
struct forwarder<T>: public std::tuple<T> {
using std::tuple<T>::tuple;
// Pointer-like accessors
auto &operator *() {
return std::get<0>(*this);
}
const auto &operator *() const {
return std::get<0>(*this);
}
auto *operator ->() {
return &std::get<0>(*this);
}
const auto *operator ->() const {
return &std::get<0>(*this);
}
};
// std::tuple_size needs to be specialized for our type,
// so that std::apply can be used.
namespace std {
template <typename... T>
struct tuple_size<forwarder<T...>>: tuple_size<tuple<T...>> {};
}
// The below two functions declarations are used by the deduction guide
// to determine whether to copy or reference the variable
template <typename T>
T forwarder_type(const T&);
template <typename T>
T& forwarder_type(T&);
// Here comes the deduction guide
template <typename... T>
forwarder(T&&... t) -> forwarder<decltype(forwarder_type(std::forward<T>(t)))...>;
虽然这看起来会产生正确的输出,但这确实会触发地址清理程序(https://godbolt.org/z/6heaxYEhE),我不确定这是否是误报。
我的问题:Fabio A.的建议正确吗?2它真的是完美地将变量捕获到lambda对象中的最佳方法吗?3我理想的解决方案应该有最少的样板,以及隐式引用语义而不是指针语义。
3条答案
按热度按时间5ktev3wc1#
使用
tuple
通过引用或值来存储参数,具体取决于它是左值还是右值(可以使用std::apply
来扩展variadic template version)Demo
yqlxgs2m2#
这可以通过在传入对象时将其 Package 在
std::reference_wrapper
中来解决,尽管在访问对象之前必须通过std::unwrap_reference_t
在lambda中展开对象。然而,请注意,这里删除了完全转发的使用,而使用了移动语义(std::reference_wrapper
统一支持)。8ljdwjyq3#
您需要处理四种情况:
int&
应保持为int&
int const&
应变为int
int&&
应变为int
int const&&
应变为int
The answer of 康桓瑋 does handle only the cases 1 and 3. If you never have constant objects, this works and is sufficient. If you want to cover the
const
cases, then you can't get around astd::conditional
.您可以按如下方式检查是否正确:
x一个一个一个一个x一个一个二个x
但是,用户很难理解此结构,并且很容易使用错误!
因此,我建议您,如果用户需要引用语义,就应该让他们显式地传递引用 Package 器,这样,
foo
就可以简化为一个完美的捕获过程,如果使用了wapper,就可以随后对其进行解 Package 。案例1 / a现在的行为方式与其他三种案例相同。
为了实现你最初想要的示例1 / a的引用行为,用户必须使用
std::reference_wrapper
来代替,我在下面将这个示例称为z。