"Effective Modern C++" 示例中在索引运算符之前使用 std::forward 的原因
Reason for using std::forward before indexing operator in "Effective Modern C++" example
引自“Effective Modern C++”的第 3 项(“理解 decltype”):
However, we need to update the template’s implementation to bring it into accord
with Item 25’s admonition to apply std::forward to universal references:
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i];
}
为什么我们在'authAndAccess'的最后一个语句中转发'c'?我理解当我们将参数从原始函数中传递给另一个函数时需要转发(以便能够正确调用采用右值引用的重载版本,或者如果存在按值采用参数的重载版本,那么我们可以移动而不是复制),但是 std::forward 在上面的例子中调用 indexing-operator 给我们带来了什么好处?
下面的示例还验证了使用 std::forward 不允许我们 return 来自 authAndAccess 的右值引用,例如,如果我们希望能够使用return 值。
#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;
}
这取决于容器是如何实现的。如果它有两个 reference-qualified operator[]
用于左值和右值,例如
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[]
重载。
当您希望您的代码尊重值类别时,您 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);
}
}
Move
Copy
为什么?因为我们将右值 原始数组 传递给函数。因此,索引表达式(转发后)尊重 []
中构建的语义。如果索引一个右值数组,您将获得一个在值类别分类法中也是右值的元素。所以我们在初始化 _1
.
时从它移动
另一方面,_2
是从左值初始化的,同样是因为我们将左值数组传递给函数。如果您想让函数内的表达式根据操作数的值类别执行它们的操作,您的代码可以 std::forward
来实现。
这并不意味着这对阳光下的每个 表达式都有意义,但它是需要注意的有用的东西。
引自“Effective Modern C++”的第 3 项(“理解 decltype”):
However, we need to update the template’s implementation to bring it into accord with Item 25’s admonition to apply std::forward to universal references:
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i)
{
authenticateUser();
return std::forward<Container>(c)[i];
}
为什么我们在'authAndAccess'的最后一个语句中转发'c'?我理解当我们将参数从原始函数中传递给另一个函数时需要转发(以便能够正确调用采用右值引用的重载版本,或者如果存在按值采用参数的重载版本,那么我们可以移动而不是复制),但是 std::forward 在上面的例子中调用 indexing-operator 给我们带来了什么好处?
下面的示例还验证了使用 std::forward 不允许我们 return 来自 authAndAccess 的右值引用,例如,如果我们希望能够使用return 值。
#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;
}
这取决于容器是如何实现的。如果它有两个 reference-qualified operator[]
用于左值和右值,例如
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[]
重载。
当您希望您的代码尊重值类别时,您 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);
}
}
Move
Copy
为什么?因为我们将右值 原始数组 传递给函数。因此,索引表达式(转发后)尊重 []
中构建的语义。如果索引一个右值数组,您将获得一个在值类别分类法中也是右值的元素。所以我们在初始化 _1
.
_2
是从左值初始化的,同样是因为我们将左值数组传递给函数。如果您想让函数内的表达式根据操作数的值类别执行它们的操作,您的代码可以 std::forward
来实现。
这并不意味着这对阳光下的每个 表达式都有意义,但它是需要注意的有用的东西。