• 有名字的一定是左值;能取地址的一定是左值。这一点最坑:

int && a = 1;

请问a是左值还是右值?a有名字,因此a是左值,a是一个右值引用,a和1 绑定。

1
2
int i = 0;
int && a = i+1;

a是一个右值引用,指向一个临时对象,那么a是右值么?不是,是左值,而且可以对a取地址。

std::cout << &a << std::endl;

什么意思?因为C++给T&&开了特例,允许给T&&传入一个左值,并根据折叠规则(T&& & = T& T&& && = T&&)进行类型推断。因此T&&是万能的reference,即universal reference。对universal reference有一个判断标准:

If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

什么意思?如果一个变量或者函数的形参T&&需要类型推导,那么他一定是universal reference。举个栗子:

1
2
template < typename T >	
void fuck(T&& t) {}

这个t是universal reference,因为他什么都接,可以是左值,也可以是右值,但是根据上一条规则,t本身是一个左值!

但是我们把他模板特化:

fuck<int>();

此时T=int,模板为: void fuck(int && t)

该函数只能接收右值。也就是说移动构造函数是对的,因为移动构造函数长这个样子:

1
2
3
class Fuck {	
	 Fuck(Fuck &&)
}

此时类型是固定的,不需要推导,因此只能接收右值。t是一个右值引用,而且t是左值。

OK,有了以上这些规则之后来看这个诡异的代码:

  • 诡异的重载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
template < typename T >
void fuck(T &f) {	
    std::cout << "lvalue" <<std::endl;
}

template <typename T>
void fuck(T &&f) {
    std::cout << "rvalue" <<std::endl;
}

template <typename T>
void superfuck(T &&f) {
	 fuck(f);
}

void main()
{
    int a;
    superfuck(a);
    superfuck(1);
}

请问这两个函数调用会输出哪个结果?答案都是lvalue。根据第一条规则,有名字的都是左值,在superfuck内f是一个左值,类型为T&,导致fuck函数只能接收左值。有的人说T&& f是universal reference啥都能收,也是candidate,此处是为了精确匹配。

那如果将引用改一个会如何?把第一个重载模板改成const T &f,会怎样?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
template < typename T >
void fuck(const T &f) {	
    std::cout << "lvalue" <<std::endl;
}

template <typename T>
void fuck(T &&f) {
    std::cout << "rvalue" <<std::endl;
}

template <typename T>
void superfuck(T &&f) {
	 fuck(f);
}

void main()
{
    int a;
    superfuck(a);
    superfuck(1);
}

答案是输出全部变成了右值!为啥?因为模板匹配的时候优先考虑没有const的candidate,此时universal reference版本变成了较好的版本。参见overload reference。本身传入的是一个非const的左值,这样通过模板推导出来的fuck(T &f)更精确一些,因此会选择输出右值的函数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int f(const int &); // overload #1
int f(int &);       // overload #2 (both references)
int g(const int &); // overload #1
int g(int);         // overload #2
int i;
int j = f(i); // lvalue i -> int& is better 
              //than lvalue int -> const int&
              // calls f(int&)
int k = g(i); // lvalue i -> const int& ranks Exact Match
              // lvalue i -> rvalue int ranks Exact Match
              // ambiguous overload: compilation error

是不是很崩溃?