You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现返回混合右值与左值引用的元组且避免左值引用退化?

实现兼容右值与左值引用的std::tuple返回值

问题描述

我需要实现一个函数,使其返回包含右值与左值引用的std::tuple,避免左值引用退化为值类型。以下是最小示例代码:

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()返回的是std::tuple<int, int>,但我期望它返回std::tuple<int, int&>。该逻辑需要适配任意元组z,其元素解引用后可能是右值或左值引用。

原本元组z的元素均类似LValueRef类,使用std::tie即可满足返回要求,但现在需要兼容解引用返回右值的元素。我想实现的逻辑大概是:对左值引用类型的元素用std::ref包裹,否则直接返回元素,但不清楚如何在std::apply调用中实现;也考虑过转换元组类型后返回std::tuple<Ts_transformed...>(*x...)。

我曾尝试使用std::forward_as_tuple,它会返回std::tuple<int&&, int&>,但此时会出现访问已销毁临时对象的问题,我不清楚具体原因,也不明白为何无法使用int&&类型的值。

我的初衷是编写类似Python的range和zip的适配器,上述函数用于解引用迭代器元组,原本迭代器均返回左值引用,std::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_);
    }
  };
  ...
}

请问实现该需求的理想方式是什么?

解决方案

核心思路是根据解引用结果的类型(左值引用/右值),在tuple中存储对应的正确类型:对于左值引用保留引用,对于右值直接存储值(避免悬垂引用)。

最小示例的修改实现

通过类型萃取和折叠表达式,为每个解引用结果生成正确的存储类型:

#include <tuple>
#include <type_traits>

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

// 辅助函数:根据解引用结果类型,返回正确的存储形式
template<typename T>
auto resolve_deref_type(T&& elem) {
    using DerefResult = decltype(*elem);
    if constexpr (std::is_lvalue_reference_v<DerefResult>) {
        return *elem; // 保留左值引用
    } else {
        return std::move(*elem); // 右值类型直接移动存储,减少拷贝
    }
}

auto f() {
    return std::apply([](auto &...elems) {
        return std::tuple{resolve_deref_type(elems)...};
    }, z);
}

int main() {
    auto [a, b] = f();
    // a是int类型,b是int&类型,符合预期
}

Zip适配器的修改实现

将同样的逻辑整合到迭代器的operator*()中:

#include <tuple>
#include <type_traits>

template <typename... WrappedRanges> class ZipWrappedRange {
  std::tuple<WrappedRanges...> r_;
public:
  class iterator {
    std::tuple<typename WrappedRanges::iterator...> its_;

    // 辅助函数:确定每个迭代器解引用后的正确存储类型
    template<typename Iter>
    static auto get_deref_value(Iter&& iter) {
        using DerefResult = decltype(*iter);
        if constexpr (std::is_lvalue_reference_v<DerefResult>) {
            return *iter; // 保留左值引用
        } else {
            return std::move(*iter); // 右值类型移动存储
        }
    }
  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 {
        return std::apply([](auto &...iters) {
            return std::tuple{get_deref_value(iters)...};
        }, its_);
    }

    // 补充迭代器必要的operator++、operator==等方法
    iterator& operator++() {
        std::apply([](auto&... iters) { (++iters, ...); }, its_);
        return *this;
    }

    bool operator==(const iterator& other) const {
        // 这里以第一个迭代器的相等性作为判断依据,可根据需求调整
        return std::get<0>(its_) == std::get<0>(other.its_);
    }
  };

  // 补充Range接口的begin()、end()方法
  iterator begin() {
      return std::apply([](auto&... ranges) {
          return iterator(std::begin(ranges)...);
      }, r_);
  }

  iterator end() {
      return std::apply([](auto&... ranges) {
          return iterator(std::end(ranges)...);
      }, r_);
  }

  explicit ZipWrappedRange(WrappedRanges&&... ranges) : r_(std::make_tuple(std::move(ranges)...)) {}
};

为什么std::forward_as_tuple会出问题?

std::forward_as_tuple会直接返回包含右值引用的tuple,比如对于RValue的解引用结果(临时int),它会存储int&&。但这个临时int在operator*()返回后就会被销毁,后续访问这个右值引用会导致悬垂引用,触发未定义行为。而我们的方案中,对于右值会直接存储其值,从根源避免了这个问题。


内容的提问来源于stack exchange,提问作者darkspine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 06:55:42