使用forward的优点 [英] Advantages of using forward

查看:158
本文介绍了使用forward的优点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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


解决方案

问题。您可以详细阅读整个问题,但我'



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






最简单的是使用lvalue- / p>

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

但是无法处理临时值: f



下一次尝试可能是:

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

这修复了上述问题,但翻转了翻牌。它现在不允许 E 有非const参数:

  int i = 1,j = 2,k = 3; 
void E(int& amp; int& amp; int&); f(i,j,k); //哎呀! E不能修改这些

第三个尝试接受const引用,但是 const_cast 是 const

  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));
}

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

  const int i = 1,j = 2,k = 3; 
E(int& amp; int& amp; int&); f(i,j,k); //哎哟! E可以修改一个const对象!

最终解决方案正确处理一切...以不可能维护为代价。您提供 f 的重载,包含const和非const的所有组合:

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

模板< 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 组合,噩梦。我们想自动这样做。



(这实际上是我们得到的编译器为我们在C ++ 11。)






在C ++ 11中,我们有机会解决这个问题。 一个解决方案修改了现有类型的模板扣除规则,但这可能会破坏大量代码。 因此,我们必须找到另一种方法。



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



如果给出引用的引用(注意引用是一个涵盖的术语,意思是 T& / code>和 T&&& ),我们使用以下规则来计算结果类型:


给定类型TR是对类型T的引用,尝试创建类型对于cv TR的lvalue引用创建类型对于T的引用值,而尝试创建类型rvalue reference to cv TR创建类型TR。


或以表格形式:

  TR R 

T& & - >夯; // lvalue reference to cv TR - >左值引用T
T& &&& - >夯; // rvalue reference to cv TR - > TR(左值引用T)
T&& & - >夯; // lvalue reference to cv TR - >左值引用T
T&& &&& - > T&& // rvalue reference to cv TR - > TR(右值引用T)

接下来,使用模板参数推导:如果参数是左值,我们为模板参数提供一个对A的引用。否则,我们正常推导。这会产生所谓的通用引用(术语

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



在代码中:

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

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

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

  void foo(int& 

template< typename T>
void deduce(T& x)
{
foo(x); // fine,foo可以引用x
}

deduce(1); // okay,foo对x的值为1的操作

这不好。 E需要得到我们得到的相同种类的价值类别!解决方案是:

  static_cast< T&&>(x); 

这做什么?考虑我们在 deduce 函数中,我们已经传递了一个左值。这意味着 T A& ,因此静态转换的目标类型为 A& &&& ,或只是 A& 。由于 x 已经是一个 A& ,我们什么也不做,留下一个左值引用。



当我们通过右值时, T A 静态强制转换的目标类型为 A&& 。转换会生成一个右值表达式,无法再传递到一个左值引用



将这些组合在一起可以得到完美转发:

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

f 一个左值, E 得到一个左值。当 f 收到右值时, E 得到右值。完美。






当然,我们想摆脱丑陋。 static_cast< T&&> 是秘密的,让我们改用一个名为 forward 的效用函数,它也做同样的事情:

  std :: forward< A>(a); 
//与
static_cast< A&&>(a)相同;


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.

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);
}

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

The next attempt might be:

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

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

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!

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 arguments require 2N combinations, a nightmare. We'd like to do this automatically.

(This is effectively what we get the compiler to do for us in 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.

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.

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] 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."

Or in tabular form:

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)

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.

In code:

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

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);

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.

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)); 
}

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


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);

这篇关于使用forward的优点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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