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

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

问题描述

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

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引用,但随后 const_cast 常量

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 的重载,以及const和非const的 all 组合:

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个参数需要2 N 组合,这是一场噩梦。我们希望自动执行此操作。

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:


[[给出]作为类型T的引用的类型TR,创建类型对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);

这是做什么的?假设我们位于 ducuce 函数中,并且已经传递了一个左值。这意味着 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.

传递右值后, T A ,因此静态类型转换的目标类型为 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< T&> 神秘而又难以记住;让我们改为创建一个名为 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天全站免登陆