【问题标题】:Reason for using std::forward before indexing operator in "Effective Modern C++" example在“Effective Modern C++”示例中索引运算符之前使用 std::forward 的原因
【发布时间】:2021-09-12 10:19:34
【问题描述】:

引自“Effective Modern C++”的第 3 条(“理解 decltype”):

但是,我们需要更新模板的实现以使其符合 与 Item 25 的告诫将 std::forward 应用于通用引用:

template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
    authenticateUser();
    return std::forward<Container>(c)[i];
}

为什么我们在“authAndAccess”的最后一条语句中转发“c”?我理解当我们将参数从原始函数中传递给另一个函数时需要转发(以便能够正确调用采用右值引用的重载版本,或者如果存在按值获取参数的重载版本,那么我们可以移动而不是复制),但是在上面的例子中调用 indexing-operator 时 std::forward 给我们带来了什么好处?

下面的示例还验证了使用 std::forward 不允许我们从 authAndAccess 返回右值引用,例如,如果我们希望能够使用返回值移动(而不是复制)。

#include <bits/stdc++.h>

void f1(int & param1) {
    std::cout << "f1 called for int &\n";
}

void f1(int && param1) {
    std::cout << "f1 called for int &&\n";
}

template<typename T>
void f2(T && param) {
    f1(param[0]);  // calls f1(int & param1)
    f1(std::forward<T>(param)[0]); // also calls f1(int & param1) as [] returns an lvalue reference
}

int main()
{
    std::vector<int> vec1{1, 5, 9};

    f2(std::move(vec1));

    return 0;
}

【问题讨论】:

    标签: c++ perfect-forwarding


    【解决方案1】:

    这取决于容器是如何实现的。如果它有两个reference-qualifiedoperator[],分别代表左值和右值

    T& operator[] (std::size_t) &; // used when called on lvalue
    T operator[] (std::size_t) &&; // used when called on rvalue
    

    然后

    template<typename Container, typename Index>
    decltype(auto) authAndAccess(Container&& c, Index i)
    {
        authenticateUser();
        return std::forward<Container>(c)[i]; // call the 1st overload when lvalue passed; return type is T&
                                              // call the 2nd overload when rvalue passed; return type is T
    }
    

    如果不转发参考可能会造成麻烦。

    template<typename Container, typename Index>
    decltype(auto) authAndAccess(Container&& c, Index i)
    {
        authenticateUser();
        return c[i]; // always call the 1st overload; return type is T&
    }
    

    然后

    const T& rt = authAndAccess(Container{1, 5, 9}, 0);
    // dangerous; rt is dangling
    

    顺便说一句,这不适用于 std::vector,因为它没有引用限定的 operator[] 重载。

    【讨论】:

      【解决方案2】:

      std::forward 当您希望您的代码尊重值类别时。最简单的方面是函数的引用参数,实际上是具有可以依赖于值类别的重载运算符的类。但它不止于此,因为值类别根植于表达式的一般工作方式,甚至是基本表达式。

      考虑这个玩具示例:

      #include <iostream>
      
      template<typename Container, typename Index>
      decltype(auto) access(Container&& c, Index i)
      {
          return std::forward<Container>(c)[i];
      }
      
      struct A {
          A() = default;
          A(A const&) { std::cout << "Copy\n"; }
          A(A &&)     { std::cout << "Move\n"; }
      };
      
      int main()
      {
          using T = A[1];
          [[maybe_unused]] auto _1 = access(T{}, 0);
          {
              T t;
              [[maybe_unused]] auto _2 = access(t, 0);
          }
      }
      

      It will print:

      Move
      Copy
      

      为什么?因为我们将一个右值 原始数组 传递到函数中。因此,索引表达式(转发后)尊重[] 中构建的语义。如果您索引一个右值数组,您将获得一个元素,该元素也是值类别分类中的一个右值。所以我们在初始化_1时从它移开。

      另一方面,

      _2 是从左值初始化的,同样因为我们将左值数组传递给函数。如果你想让函数内部的表达式根据操作数的值类别来做他们的事情,你的代码可以std::forward 来完成。

      这并不意味着这对太阳下的每一个表达式都有意义,但需要注意这一点。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-11-03
        • 1970-01-01
        • 1970-01-01
        • 2021-08-16
        • 2018-01-18
        • 1970-01-01
        • 2021-08-01
        • 1970-01-01
        相关资源
        最近更新 更多