Minimum Example:
struct RValue {
int x = 12;
auto operator*() const { return x; }
} x;
struct LValueRef {
int y = 13;
auto &operator*() { return y; }
} y;
auto z = std::make_tuple(x, y);
auto f() {
// Replace std::make_tuple with what?
return std::apply([](auto &...x) { return std::make_tuple(*x...); }, z);
}
int main() {
auto [a, b] = f();
}
I would like f to return a std::tuple<int, int&>
, where it is currently returning a std::tuple<int, int>
. This is supposed to run on an arbitrary tuple z, where elements on de-referencing can give r-values and l-value references.
Originally, all elements of the tuple z were similar to the LValueRef
class, so I used std::tie
to return the tuple in the form required.
I essentially need to call std::make_tuple
, but wrap a std::ref
around the elements which are originally l-vale references. This could be done as the following pseudo code shows:
if constexpr (std::is_lvalue_reference<T>::value)
return std::ref(t);
else
return t;
I don't know exactly how to write this in the std::apply
invocation, but maybe I can transform the types of the std::tuple
and create return return std::tuple<Ts_transformed...>(*x...);
What is the ideal way to get this behavior?
I briefly tried to use std::forward_as_tuple
without understanding how it works (it compiled). this caused r-values to become r-value references and l-values to become l-value references (i.e., returns std::tuple<int &&, int &>
. However, when using this, I believe I was accessing a temporary after it went out of scope, and that isn't correct. I'm not sure what exactly is happening here, and why I cannot use the int &&
value?
I was originally trying to write an adaptor similar to Python's range
and zip
. The function I am trying to write above de-references a tuple of iterators. Originally the iterators all returned lvalue-references, so std::tie
worked fine, but I wanted this to also work with iterators that return rvalues.
template <typename... WrappedRanges> class ZipWrappedRange {
std::tuple<WrappedRanges...> r_;
public:
class iterator {
std::tuple<typename WrappedRanges::iterator...> its_;
public:
explicit iterator(typename WrappedRanges::iterator &&...it) : its_(std::make_tuple(it...)) {}
explicit iterator(std::tuple<typename WrappedRanges::iterator...> &&it) : its_(it) {}
auto operator*() const {
// I need to modify this to allow for cases where (*x) returns rvalues and not just l-value references.
return std::apply([](auto &...x) { return std::tie((*x)...); }, its_);
}
};
...
}
std::make_tuple
might be replaced with tuple
constructor where you provide types explicitly:
auto f() {
return std::apply([](auto&...x) { return std::tuple<decltype(*x)...>{*x...}; },
z);
}