为什么Rvalue引用被通用引用转换为左值引用

时间:2016-12-28 11:13:30

标签: c++ c++11 rvalue-reference perfect-forwarding lvalue-to-rvalue

我认为当通用引用参数与rvalue引用参数匹配时,将返回rvalue引用参数。但是,我的测试显示rvalue引用被通用引用函数模板转换为左值引用。为什么会这样?

#include <iostream>
#include <type_traits>
using namespace std;
template <typename T>
T f1(T&&t) {  //<-----this is a universal reference
  cout << "is_lvalue reference:" << is_lvalue_reference<T>::value << endl;
  cout << "is_rvalue reference:" << is_rvalue_reference<T>::value << endl;
  cout << "is_reference:"        << is_reference<T>::value        << endl;
  return t;
}
void f2(int&& t) {
  cout << "f2 is_lvalue reference:" << is_lvalue_reference<decltype(t)>::value << endl;
  cout << "f2 is_rvalue reference:" << is_rvalue_reference<decltype(t)>::value << endl;
  cout << "f2 is_reference:" << is_reference<decltype(t)>::value << endl;
  f1(t);

}

int main()
{
  f2(5);
  return 0;
}

在GCC和VC ++ 2010中,结果如下:

f2 is_lvalue reference:0
f2 is_rvalue reference:1
f2 is_reference:1
is_lvalue reference:1
is_rvalue reference:0
is_reference:1

换句话说,t中的参数f2是右值引用,但是当传递给f1时,参数变为左值引用。它不应该保留f1中的右值 -

4 个答案:

答案 0 :(得分:6)

原因是命名的右值引用被视为左值。

t 传递给 f1 以保留右值时,您应该在 f2 中使用std::move

void f2(int&& t) {
    f1(std::move(t));
}

Here你可以找到一个很好的解释。

答案 1 :(得分:3)

调用f1(t),参数是表达式t。不是static_cast<decltype(t)>(t)或者其他什么。您对decltype(t)的检查与f1(t)的调用无关。

表达式t的类型为int,值类别为左值。 (根据经验,如果你可以获取表达式的地址,那么它就是一个左值,你当然可以写&t)。 &#34;信息&#34;引用变量最初被声明为引用只能通过decltype检查才能看到。

由于使用左值调用f1T推断为int&

NB。如果您希望在f1中看到decltype(t)成立,您可能希望T也使用is_rvalue_reference而不是f1。对于右值参数,T推导为非引用类型,例如如果您通过f2进行f1(std::move(t));修正f1,则T int decltype(t) f1int&& def baseDir = System.getProperty( 'catalina.base' ) def imagesDir = "${baseDir}\webapps\myapp\images" }是@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkPermission()) { //do your work } else { requestPermission(); } } } protected boolean checkPermission() { int result = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE); if (result == PackageManager.PERMISSION_GRANTED) { return true; } else { return false; } } protected void requestPermission() { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { Toast.makeText(this, "Write External Storage permission allows us to do store images. Please allow this permission in App Settings.", Toast.LENGTH_LONG).show(); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100); } } } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case 100: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //do your work } else { Log.e("value", "Permission Denied, You cannot use local drive ."); } break; } }

答案 2 :(得分:0)

在研究了C ++ 11标准之后,我对f1(t); f2之后发生的事情有了一个模糊的概念。我在这里描述一下,看看我是否做对了:

  1. f2中,tint&&类型的左值(不是int,这是一个重要区别)
  2. 调用f1(t);导致类型被推断为:

    2.1当T中的f1被赋予左值时,它被推断为该左值的类型或int&& &

    的引用

    2.2参考折叠导致int&& &成为int &。这是T

  3. 的值
  4. 由于f1的参数被声明为T&&,因此t中参数f1的类型为int & &&。因此,引用折叠是第二次将t的类型推断为int &

  5. 因此,T = int &,参数t的类型为int &。即参数tint &

  6. 类型的左值

    有任何评论吗?

答案 3 :(得分:-1)

使用 std::forward 将 t 保持为右值。

void f2(int&& t) {
  //....
  f1(std::forward<int>(t));
}

t 参数作为右值传递并推导出为 int 类型。 (看看如何扣除here

std::forward(t) 返回一个 int&& 类型的右值,以便调用 f1(int&&)

没有 std::forward(t),f1(t) 接受 int 类型的参数并调用 f1(int&)

更新:请注意

之间的区别
 is_lvalue_reference<T> and is_lvalue_reference<decltype<t>>. 

在模板中,T是根据函数参数推导出来的,而t的值类别在f2中总是左值,

forward<int>(t) and move(t) 

总是右值。

#include <iostream>
#include <type_traits>

class A {};


template <typename T>
T f0(T& t) {  //<-----this is a universal reference
    std::cout<< "f0 lvalue"<<'\n';

    // take T as template argument, it is type int, so not rvalue, not lvalue.
//  std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
    std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
    std::cout << "is_rvalue reference:" << std::is_rvalue_reference<T>::value << std::endl;
    std::cout << "is_reference:"        << std::is_reference<T>::value        << std::endl;
    return t;
}

template <typename T>
T f0(T&&t) {  //<-----this is a universal reference
    std::cout<< "f0 rvalue"<<'\n';
    std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
    std::cout << "is_rvalue reference:" << std::is_rvalue_reference<T>::value << std::endl;
    std::cout << "is_reference:"        << std::is_reference<T>::value        << std::endl;
    return t;
}

template <typename T>
T f1(T& t) {  //<-----this is a universal reference
    std::cout<< "f1 lvalue"<<'\n';

    // take T as template argument, it is type int, so not rvalue, not lvalue.
//  std::cout << "is_lvalue reference:" << std::is_lvalue_reference<T>::value << std::endl;
    std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
    std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
    std::cout << "is_reference:"        << std::is_reference<decltype(t)>::value        << std::endl;
    return t;
}

template <typename T>
T f1(T&&t) {  //<-----this is a universal reference
    std::cout<< "f1 rvalue"<<'\n';
    std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
    std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
    std::cout << "is_reference:"        << std::is_reference<decltype(t)>::value        << std::endl;
    return t;
}

void f2(int&&t) {  //<-----this is a universal reference
    std::cout<< "f2 rvalue"<<'\n';
    std::cout << "is_lvalue reference:" << std::is_lvalue_reference<decltype(t)>::value << std::endl;
    std::cout << "is_rvalue reference:" << std::is_rvalue_reference<decltype(t)>::value << std::endl;
    std::cout << "is_reference:"        << std::is_reference<decltype(t)>::value        << std::endl;


    f1(std::forward<int>(t));   // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
    f1(std::move(t));           // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
    f1(t);                      // T is deduced as int for f(T&),  type int is not rvalue, nor lvalue, t is lvalue
                                //if f1(T&) not exist, then f1(t) will call f1(T&&), T is deduced as int&, type int& is lvalue, t is lvalue


    f0(std::forward<int>(t));   // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
    f0(std::move(t));           // T is deduced as int for f(T&&), type int is not rvalue, nor lvalue, t is rvalue
    f0(t);                      // T is deduced as int for f(T&),  type int is not rvalue, nor lvalue, t is lvalue
                                //if f0(T&) not exist, then f0(t) will call f0(T&&), T is deduced as int&, type int& is lvalue, t is lvalue
}




void test_rvalue()
{

    f2(5);
    std::cout << std::boolalpha;
    std::cout << std::is_lvalue_reference<A>::value << '\n';        // A is not lvalue
    std::cout << std::is_rvalue_reference<A>::value << '\n';        // A is not rvalue
    std::cout << std::is_lvalue_reference<A&>::value << '\n';       // A& is lvalue
    std::cout << std::is_rvalue_reference<A&&>::value << '\n';      // A&& is rvalue
    std::cout << std::is_lvalue_reference<int>::value << '\n';      // int is not lvalue
    std::cout << std::is_rvalue_reference<int>::value << '\n';      // int is not rvalue
    std::cout << std::is_lvalue_reference<int&>::value << '\n';     // int& is lvalue
    std::cout << std::is_rvalue_reference<int&&>::value << '\n';    // int&& is rvalue

}