空类按值传递支持constexpr的标准依据与左值转右值转换疑问
空类与非空类对象在constexpr函数传值中的常量表达式差异解析
问题背景
我编写了如下C++代码,发现一个现象:当类包含非静态数据成员时,其对象按值传递给constexpr函数后,无法用于常量表达式上下文。查阅资料时看到GCC Bug#114219的讨论,有用户认为空类对象按值传递时不会触发左值到右值转换,这是示例中空类A可行、非空类B不可行的原因,但也有用户反对这个观点。
我需要明确两个核心问题:
- C++标准是如何允许空类案例生效,却禁止非空类案例的?
- “空类会绕过左值到右值转换”这一说法是否正确?对应的标准依据是什么?
代码示例
struct A { static constexpr int value = 42; }; struct B { static constexpr int value = 42; int mem{}; }; constexpr int f(A a) { return A::value; } constexpr int f(B b) { return B::value; } int main() { A a; constexpr int aconst = f(a); // 所有编译器均正常通过 B b; constexpr int bconst = f(b); // 所有编译器均报错,原因何在? }
GCC报错信息
<source>: In function 'int main()': <source>:18:31: error: the value of 'b' is not usable in a constant expression 18 | constexpr int bconst = f(b); //fails in all compilers, why? | ^ <source>:17:7: note: 'b' was not declared 'constexpr' 17 | B b; |
标准规则与差异原因
1. 常量表达式对对象操作的核心要求
C++标准规定,在常量表达式上下文(比如初始化constexpr变量)中,使用对象时必须满足:要么该对象本身是常量表达式的结果,要么对它的操作不需要依赖无法在编译时确定的值。
当调用f(b)时,b是一个非constexpr的自动对象,按值传递会触发复制初始化:
- 对于非空类B:复制初始化临时B对象时,需要初始化其非静态成员
mem。由于原对象b不是constexpr,mem的值无法在编译时确定,整个复制操作无法在常量表达式中完成,因此f(b)不能作为常量表达式。 - 对于空类A:复制初始化临时A对象时,因为类没有非静态数据成员,其默认构造和复制构造都是平凡操作,不需要访问原对象
a的任何成员值。此时复制操作不依赖a的具体值,编译器可以在编译时完成,不会触发对a的求值需求。
2. “空类绕过左值到右值转换”说法的验证
这个说法不够精准,准确表述是:空类对象的复制操作不需要读取其成员值,因此不会触发左值到右值转换的需求。
根据C++标准中左值到右值转换的规则:只有当需要获取对象的具体值时,才会触发该转换。对于空类对象,复制它不需要读取任何成员,因此不需要对原对象进行左值到右值转换;而非空类B的复制需要处理非静态成员mem,必须依赖原对象b的成员值,而b不是constexpr,无法在常量表达式中提供合法值,因此触发错误。
对应的标准依据:
- 标准[expr.const]章节明确,常量表达式中不能包含“对非常量表达式对象进行左值到右值转换”的操作,除非该对象是平凡类型且生命周期始于常量表达式求值期间。
- 空类的复制构造属于平凡构造,构造过程中未引用任何非静态成员的值,因此不需要对原对象求值(即左值到右值转换),符合常量表达式的要求;非空类的复制构造必须处理非静态成员,依赖原对象的成员值,而原对象不是
constexpr,不满足常量表达式的约束。
内容的提问来源于stack exchange,提问作者user20562802
相关产品推荐
相关产品推荐

