Clang与GCC对连续类型转换的解析差异及代码合法性判定
先看我们讨论的核心代码:
#include <iostream> int& addone(int& r) { return ++r; } int main() { std::cout << addone((int&)(int&&)7) << std::endl; }
问题核心拆解
这段代码的关键是两步连续的引用转换:
- 把字面量
7(一个prvalue纯右值)转换为int&&右值引用,得到一个xvalue将亡值,属于glvalue广义左值的一种; - 再把这个xvalue转换为
int&非const左值引用,传递给要求左值引用参数的addone函数。
Clang在开启-Wall -Wextra -Werror -pedantic这类严格编译选项时,能正常编译运行并输出8;但GCC即便加上-fpermissive宽松选项也完全拒绝编译——到底哪种处理符合C++标准?
标准依据分析
我们可以从C++标准的关键章节找到明确答案:
- xvalue到左值引用的转换合法性
根据C标准(以C20为例,[expr.static.cast] §7.6.1.9/13):
一个类型为T1的glvalue表达式可以被转换为“T2的引用”类型,当且仅当“指向T1的指针”类型可以通过static_cast显式转换为“指向T2的指针”类型。转换结果指向与原glvalue相同的对象,但使用指定的类型。
这里的(int&&)7是xvalue(属于glvalue),它对应的指针类型是int*;而int&对应的指针类型同样是int*,因此static_cast<int*>(&((int&&)7))是合法的,对应的引用转换static_cast<int&>((int&&)7)自然也符合标准。代码里的C风格转换(int&)在这里等价于static_cast,所以这一步转换完全合规。
- 临时对象的生命周期保障
字面量7是prvalue,转换为int&&时会创建一个临时int对象并绑定到这个右值引用上。根据标准:
根据C++标准[class.temporary] §6.7.7/6:
如果引用绑定到的glvalue是通过以下方式得到的,那么引用所绑定的临时对象(或者引用所绑定的子对象所属的完整临时对象)的生命周期会延长至引用的生命周期结束:
[...]
— 将右值转换为引用类型的转换([expr.cast])
这里的两步转换都属于“将右值转换为引用类型”的场景,因此临时对象的生命周期会延长到addone返回的引用被使用完毕(也就是整个cout表达式执行结束),所以++r的操作是安全的,不会访问已销毁的对象。
结论
Clang的处理符合C++标准,GCC的拒绝编译属于超出标准的严格检查。从标准文本来看,这段代码的转换逻辑和临时对象生命周期的处理都是完全合法的。
内容的提问来源于stack exchange,提问作者sp2danny

