使用 std::forward 的主要目的是什么以及它解决了哪些问题? [英] What are the main purposes of using std::forward and which problems it solves?

查看:41
本文介绍了使用 std::forward 的主要目的是什么以及它解决了哪些问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在完美转发中,std::forward 用于将命名的右值引用 t1t2 转换为未命名的右值引用.这样做的目的是什么?如果我们离开 t1 & 这将如何影响被调用的函数 innert2 作为左值?

In perfect forwarding, std::forward is used to convert the named rvalue references t1 and t2 to unnamed rvalue references. What is the purpose of doing that? How would that affect the called function inner if we leave t1 & t2 as lvalues?

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

推荐答案

你必须了解转发问题.您可以详细阅读整个问题,但我会总结一下.

You have to understand the forwarding problem. You can read the entire problem in detail, but I'll summarize.

基本上,给定表达式 E(a, b, ... , c),我们想要表达式 f(a, b, ... , c) 是等价的.在 C++03 中,这是不可能的.有很多尝试,但都在某些方面失败了.

Basically, given the expression E(a, b, ... , c), we want the expression f(a, b, ... , c) to be equivalent. In C++03, this is impossible. There are many attempts, but they all fail in some regard.

最简单的是使用左值引用:

The simplest is to use an lvalue-reference:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
    E(a, b, c);
}

但这无法处理临时值:f(1, 2, 3);,因为它们不能绑定到左值引用.

But this fails to handle temporary values: f(1, 2, 3);, as those cannot be bound to an lvalue-reference.

下一次尝试可能是:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(a, b, c);
}

这解决了上述问题,但会失败.它现在无法允许 E 具有非常量参数:

Which fixes the above problem, but flips flops. It now fails to allow E to have non-const arguments:

int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these

第三次尝试接受常量引用,但随后 const_castconst 离开了:

The third attempt accepts const-references, but then const_cast's the const away:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}

这接受所有值,可以传递所有值,但可能会导致未定义的行为:

This accepts all values, can pass on all values, but potentially leads to undefined behavior:

const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!

最终的解决方案可以正确处理所有事情……但代价是无法维护.您提供 f 的重载,以及常量和非常量的所有组合:

A final solution handles everything correctly...at the cost of being impossible to maintain. You provide overloads of f, with all combinations of const and non-const:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);

N 个参数需要 2N 个组合,这是一场噩梦.我们希望自动执行此操作.

N arguments require 2N combinations, a nightmare. We'd like to do this automatically.

(这实际上是我们让编译器在 C++11 中为我们做的事情.)

(This is effectively what we get the compiler to do for us in C++11.)

在 C++11 中,我们有机会解决这个问题.一种解决方案修改现有类型的模板推导规则,但这可能会破坏大量代码. 所以我们得另辟蹊径.

In C++11, we get a chance to fix this. One solution modifies template deduction rules on existing types, but this potentially breaks a great deal of code. So we have to find another way.

解决方案是使用新添加的rvalue-references;我们可以在推导右值引用类型时引入新规则并创建任何所需的结果.毕竟,我们现在不可能破解代码.

The solution is to instead use the newly added rvalue-references; we can introduce new rules when deducing rvalue-reference types and create any desired result. After all, we cannot possibly break code now.

如果给定一个引用(注意引用是一个包含T&T&&的包含术语),我们使用以下规则来计算结果类型:

If given a reference to a reference (note reference is an encompassing term meaning both T& and T&&), we use the following rule to figure out the resulting type:

"[given] 类型 TR 是对类型 T 的引用,尝试创建类型对 cv TR 的左值引用"会创建类型对 T 的左值引用",而尝试创建类型对 cv TR 的右值引用"创建了类型 TR."

"[given] a type TR that is a reference to a type T, an attempt to create the type "lvalue reference to cv TR" creates the type "lvalue reference to T", while an attempt to create the type "rvalue reference to cv TR" creates the type TR."

或以表格形式:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

接下来,使用模板参数推导:如果参数是左值 A,我们为模板参数提供对 A 的左值引用.否则,我们正常推导.这给出了所谓的通用参考(术语转发参考现在是官方参考.

Next, with template argument deduction: if an argument is an lvalue A, we supply the template argument with an lvalue reference to A. Otherwise, we deduce normally. This gives so-called universal references (the term forwarding reference is now the official one).

为什么这很有用?因为结合起来我们保持跟踪类型的值类别的能力:如果它是左值,我们有一个左值引用参数,否则我们有一个右值引用参数.

Why is this useful? Because combined we maintain the ability to keep track of the value category of a type: if it was an lvalue, we have an lvalue-reference parameter, otherwise we have an rvalue-reference parameter.

在代码中:

template <typename T>
void deduce(T&& x); 

int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)

最后一件事是转发"变量的值类别.请记住,一旦在函数内部,参数就可以作为左值传递给任何东西:

The last thing is to "forward" the value category of the variable. Keep in mind, once inside the function the parameter could be passed as an lvalue to anything:

void foo(int&);

template <typename T>
void deduce(T&& x)
{
    foo(x); // fine, foo can refer to x
}

deduce(1); // okay, foo operates on x which has a value of 1

那不好.E 需要获得与我们获得的相同类型的值类别!解决办法是这样的:

That's no good. E needs to get the same kind of value-category that we got! The solution is this:

static_cast<T&&>(x);

这是做什么的?假设我们在 deduce 函数中,并且我们已经传递了一个左值.这意味着 T 是一个 A&,因此静态转换的目标类型是 A&&&,或者只是A&.由于 x 已经是 A&,我们什么都不做,只剩下一个左值引用.

What does this do? Consider we're inside the deduce function, and we've been passed an lvalue. This means T is a A&, and so the target type for the static cast is A& &&, or just A&. Since x is already an A&, we do nothing and are left with an lvalue reference.

当我们传递了一个右值时,TA,所以静态转换的目标类型是 A&&.强制转换产生一个右值表达式,不能再传递给左值引用.我们维护了参数的值类别.

When we've been passed an rvalue, T is A, so the target type for the static cast is A&&. The cast results in an rvalue expression, which can no longer be passed to an lvalue reference. We've maintained the value category of the parameter.

把这些放在一起给了我们完美的转发":

Putting these together gives us "perfect forwarding":

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

f 接收到一个左值时,E 得到一个左值.当 f 收到一个右值时,E 会得到一个右值.完美.

When f receives an lvalue, E gets an lvalue. When f receives an rvalue, E gets an rvalue. Perfect.

当然,我们想摆脱丑陋.static_cast 是神秘而奇怪的记忆;让我们创建一个名为 forward 的实用函数,它做同样的事情:

And of course, we want to get rid of the ugly. static_cast<T&&> is cryptic and weird to remember; let's instead make a utility function called forward, which does the same thing:

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);

这篇关于使用 std::forward 的主要目的是什么以及它解决了哪些问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆