【问题标题】:C++ perfect forwarding: how to avoid dangling referencesC++ 完美转发:如何避免悬空引用
【发布时间】:2019-11-08 14:04:37
【问题描述】:

考虑以下问题:我有许多类,每个类都实现一个get() 函数。以下container1container2 是此类类的示例:

struct expensive_type {
    int v;
};

struct container1 {
    expensive_type get() const {
        return { 1 };
    }
};

struct container2 {
    expensive_type x;
    expensive_type& get() {
        return x;
    }
};

我想创建一个包装器,以CF 为模板,实现与C 相同的get() 功能,但将函数应用于结果:

template<typename C, typename F>
struct wrapper {
    F f;
    C c;
    decltype(auto) get() {
        return f(c.get());
    }
};

我现在想为一个简单的包装器创建一个函数f,它只是简单地返回其参数不变。我认为这会起作用:

auto f = [](auto&& x) -> decltype(auto) {
    return forward<decltype(x)>(x);
};

wrapper<container1, decltype(f)> trivial_wrapper1 { f, {} };
wrapper<container2, decltype(f)> trivial_wrapper2 { f, {} };

,但不幸的是trivial_wrapper1.get() 返回expensive_type{0} 而不是expensive_type{1}(至少带有-O2 标志)。我猜这个问题与悬空引用有关,但我不知道如何解决它。

我的问题是:如何正确实现函数f,使其充当完美的身份,而无需复制其参数?

为了澄清,以下是预期行为的示例:

cout << trivial_wrapper1.get().v << endl; // should print 1, prints 0 as of now
trivial_wrapper2.get().v = 2;
cout << trivial_wrapper2.c.x.v << endl; // should print 2, and it does as of now

【问题讨论】:

    标签: c++ perfect-forwarding


    【解决方案1】:

    这里的问题是没有通过函数进行临时扩展。当你这样做时

    return f(c.get());
    

    ccontainer1 时,您按值返回,因此您有临时的。该临时仅存在于完整表达式的末尾,这意味着它会在 return 语句完成后死亡。这就是为什么你有一个悬空引用。

    不过,这让你有点纠结。 f 需要做的是按价值返回,如果它有一个临时的,但这可能会复制,这可能很昂贵。如果您要通过中间函数传递返回值,那么确实没有办法解决这个问题。这会给你一个f 喜欢

    auto f = [](auto&& x) -> std::conditional_t<std::is_rvalue_reference_v<decltype(x)>,
                                                std::remove_reference_t<decltype(x)>, 
                                                decltype(x)> {
        return x;
    };
    

    右值按值返回,左值按引用返回。

    【讨论】:

      【解决方案2】:

      问题是值被转换为右值引用。当您转发 into 函数时,这通常很好,但您尝试转发 out 返回值。

      而不是auto&amp;&amp; 类型的推导,你需要decltype(auto) 推导。

      但确实,您将decltype(auto) 用作每个位置,但您不能使用的位置:auto&amp;&amp; lambda 参数。

      我的解决方案是在get() 按值返回时衰减副本。

      template<typename C, typename F>
      struct wrapper {
          F f;
          C c;
          decltype(auto) get() {
              using get_t = decltype(c.get());
              using f_result_t = decltype(f(c.get()));
      
              if constexpr (std::is_reference_v<get_t>) {
                  return f(c.get());
              } else {
                  return std::decay_t<f_result_t>(f(c.get())); // decay copy
              }
          }
      };
      

      解决方案并不明显。当你这样做时会发生什么?

      auto f = [](auto&& x) -> decltype(auto) {
          static auto v = std::decay_t<decltype(x)>{};
          return (v); // return by ref
      };
      
      wrapper<container1, decltype(f)> trivial_wrapper1 { f, {} };
      wrapper<container2, decltype(f)> trivial_wrapper2 { f, {} };
      

      现在,即使没有衰减副本,它也会按预期工作。如果你使用衰减副本,你必须写出函数的意图以及它应该如何表现。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-02-09
        • 2019-11-19
        • 1970-01-01
        • 2022-01-22
        • 1970-01-01
        • 2011-04-05
        • 1970-01-01
        相关资源
        最近更新 更多