What's the cleanest way to perfectly forward arguments into a lambda capture in C++20/C++23? By this I mean capturing rvalues by copy and lvalues by reference, inside of the coroutine object:
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 seems to suggest that the above should work, but the above program (https://godbolt.org/z/Mz3caah5o) results in
1
2
3
0 <- should be 3
1
A blog post by Vittorio Romeo uses macros to achieve the desired effect. One downside is that the capture uses pointer semantics, rather than the implicit semantics of references. In this answer Fabio A. suggests a simpler method using deduction guides:
// 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)))...>;
While this seems to result in the correct output, this does trigger the address sanitizer (https://godbolt.org/z/6heaxYEhE), and I'm not sure whether this is a false positive.
My question: is the suggestion by Fabio A. correct, and is it indeed the best way to perfectly capture variables into a lambda object? My ideal solution would have minimal boilerplate, and also implicit reference semantics rather than pointer semantics.