调用返回右值和左值元组混合的函数,而左值引用不会衰减为值?

Invoke functions that return a mix of rvalues and lvalue tuple without the lvalue references decaying into values?

提问人:darkspine 提问时间:11/1/2023 更新时间:11/2/2023 访问量:51

问:

最小示例:

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();
}

我希望 f 返回 a ,它当前返回 a .这应该在任意元组 z 上运行,其中取消引用的元素可以给出 r 值和 l 值引用。std::tuple<int, int&>std::tuple<int, int>

最初,元组 z 的所有元素都类似于类,因此我过去常常以所需的形式返回元组。LValueRefstd::tie

我基本上需要调用 ,但将 a 包裹在最初是 l-vale 引用的元素周围。这可以按照以下伪代码所示完成:std::make_tuplestd::ref

  if constexpr (std::is_lvalue_reference<T>::value)
    return std::ref(t);
  else
    return t;

我不知道如何在调用中写这个,但也许我可以转换 的类型并创建返回std::applystd::tuplereturn std::tuple<Ts_transformed...>(*x...);

获得这种行为的理想方法是什么?


我短暂地尝试在不了解它如何工作的情况下使用(它编译了)。这导致 R 值成为 R 值引用,L 值成为 L 值引用(即,返回 .但是,在使用它时,我相信我在它超出范围后访问了一个临时文件,这是不正确的。我不确定这里到底发生了什么,为什么我不能使用这个值?std::forward_as_tuplestd::tuple<int &&, int &>int &&


我最初试图编写一个类似于 Python 的适配器和 .我试图在上面编写的函数取消了对迭代器元组的引用。最初迭代器都返回左值引用,所以工作正常,但我希望它也适用于返回 rvalues 的迭代器。rangezipstd::tie

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_);
    }
  };
  ...
}
C++ 模板 C++17

评论

0赞 Red.Wave 11/1/2023
您是否正在尝试重塑 C++20/23?en.cppreference.com/w/cpp/ranges/zip_view<ranges>
0赞 darkspine 11/1/2023
是的,至少部分是这样。我不能在这个项目中使用C++23,我不想依赖编译器版本来包含gcc的标头。
0赞 Red.Wave 11/1/2023
你试过吗?它是由Eric Neibler维护的原型。github.com/ericniebler/range-v3range-v3<ranges>
1赞 Igor Tandetnik 11/2/2023
你可以编写一个帮助程序函数来解决问题,然后执行constexprstd::make_tuple(my_ref_wrapper(*x)...);
0赞 darkspine 11/2/2023
我再次尝试了辅助函数技巧,我意识到我最初是按值传递的。如果我将其作为 r 值参考,它就会起作用。

答:

1赞 Jarod42 11/2/2023 #1

std::make_tuple可能会替换为构造函数,其中显式提供类型:tuple

auto f() {
  return std::apply([](auto&...x) { return std::tuple<decltype(*x)...>{*x...}; },
                    z);
}

演示

0赞 darkspine 11/2/2023 #2

使用 constexpr 包装器函数技巧的替代解决方案。

template <typename T> auto get_value(T &&t) {
  if constexpr (std::is_lvalue_reference<T>::value)
    return std::ref(t);
  else
    return t;
}

auto f() {
  return std::apply([](auto &...x) { return std::make_tuple(get_value(*x)...); }, z);
}

演示