glvalue是否仅为带内存地址的prvalue?绑定引用后二者能否区分?
这个结论不成立,本质是被网上过度简化的“有地址是左值、没地址是右值”的入门说法误导了,也把C早期的lvalue/rvalue二分模型,和C11之后重构的完整值类别体系搞混了。
首先要先把最核心的前提说透:值类别是表达式的属性,不是内存里实体的属性。你提到的“有没有内存地址”是实体的存储属性,和表达式值类别没有绝对的绑定关系,拿这个当划分标准一定会踩坑。
三个核心基础值类别的划分逻辑其实非常直白:
- glvalue(广义左值)的核心判定标准是「有身份(identity)」:也就是这个表达式明确指代一个可以被独立识别的实体,你能判断两个glvalue是不是指代同一个东西。它和有没有内存地址没有必然联系:比如位域是glvalue,但你根本没法对它取地址;函数名、枚举项也都是glvalue,多数场景下根本不会在栈/堆上分配可寻址的内存。
- prvalue(纯右值)的核心判定标准是「用于初始化」:它本身不指代某个已经存在的可识别实体,表达的是“用来初始化一个对象/给对象赋值的值”,比如字面量
123、算术表达式a+b的结果、非引用返回值的函数调用结果,都属于prvalue。
你之前看到的“有内存地址就是glvalue”只是个反向推导的经验结论,不是本质:当你尝试对prvalue做取地址、绑定到引用这类操作时,编译器会自动触发临时量实质化,把prvalue转换成一个指代新生成的临时对象的glvalue,这时候你拿到的地址是那个临时对象的,不是说原来的prvalue本身“长了地址”。
不存在“带内存地址的prvalue”这类东西——prvalue只要成为了指代某个有存储位置实体的表达式,就已经被转换为glvalue了。glvalue和prvalue是互斥的基础分类,不存在谁是谁加属性的从属关系。
对应原文的中文翻译如下:
左值(l-value)通常指代内存中的存储位置。例如前述示例中的变量
daysInYear本质是指向内存位置的句柄,属于左值。相对地,右值(r-value)可以是内存位置存储的实际内容。因此所有左值都可作为右值使用,但并非所有右值都能作为左值使用。
这段是C++11标准出台前,面向入门者的经典简化解释,用来理解最朴素的左值右值差异没问题,但套到现在的glvalue/prvalue/xvalue体系上是不严谨的:它提到的“左值可以当右值用”,本质是编译器自动做了glvalue到prvalue的左值转换,不是说glvalue本身是prvalue的一种特殊形态。
完全可以区分,而且边界非常清晰,不存在“绑定后就混为一谈”的情况。
首先要纠正一个常见的认知偏差:根本不存在“prvalue直接绑到引用上”的状态。当你写出int&& ref = 42;这样的代码时,编译器首先会对42这个prvalue做临时量实质化,生成一个int类型的临时对象,再让右值引用ref绑定到这个临时对象上。这时候你写表达式ref的时候,它的值类别是lvalue(glvalue的子类),根本不是prvalue。
值类别的判定永远只看表达式本身的属性,和它绑定的引用类型、引用背后绑定的实体来源没有关系:
- 所有有名字的变量(包括引用变量)作为单独表达式出现时,都是lvalue,哪怕它是右值引用类型也一样。比如你写
int&& ref2 = ref;一定会编译报错,因为ref是lvalue,不能直接绑定到右值引用上,必须用std::move把它转换成xvalue(同样属于glvalue子类)才能完成绑定。 - 用
decltype做判定的时候也能看出明确差异:decltype(ref)会得到int&&(这是变量ref本身的类型),但decltype((ref))会得到int&——因为加了括号的(ref)是指代ref这个变量实体的lvalue表达式。
说白了,只要你按照值类别的核心判定规则(有没有身份、能不能被移动)去判断,不管值有没有绑定到引用、绑定到哪种引用,都不会出现无法区分的情况。那种“绑定引用后就没区别”的说法,本质是把引用绑定的实体,和表达式本身的值类别搞混了。
内容的提问来源于stack exchange,提问作者tempdev nova

