我有以下代码:
main.cpp
#include <cstdint>
#include <type_traits>
enum class FooEnum : uint8_t{
Foo1 = 0,
Foo2 = 1
};
constexpr uint32_t& operator|= (uint32_t& lhs, FooEnum rhs) {
return lhs |= 1u << static_cast<uint8_t>(rhs);
}
int main() {
uint32_t bar{0};
bar|=FooEnum::Foo1;
}
所以本质上,|=
运算符应该采用枚举并设置位,其位置对应于它的整数值。
当在 fedora 21 上用 clang++ 3.5.0 编译时,一切正常,但是当用 g++ 4.9.2 编译时,它抛出一个错误,说这不是一个常量表达式:
main.cpp: In function ‘constexpr uint32_t& operator|=(uint32_t&, FooEnum)’:
main.cpp:16:2: error: expression ‘(lhs = (lhs | (1u << ((int)rhs))))’ is not a constant-expression
}
^
对于所有类型的编译器标志组合都是如此,但是您可以例如用 g++ -std=c++11 -o a.out main.cpp
测试它(c++14 没有区别)
所以我的问题是:
- 哪个编译器是正确的(为什么)?
- 有没有办法实现
operator|=
以便 g++ 接受它作为constexpr
?
编辑:
如果您想知道,为什么我首先尝试将运算符声明为 constexpr
,尽管这在示例中不是必需的:
在我的实际代码中,我使用 |=
运算符来实现 (constexpr) |
运算符,我想在 constexpr 表达式中使用它,但在此之前,我偶然发现了两个编译器之间的差异,没有意识到 gcc4.9 并不完全支持 c++14(但接受 -std=c++14
标志).
当使用运算符实际初始化全局 constexpr 变量时,即使是 clang 也只会使用 c++14 标志对其进行编译。
最佳答案
表达式lhs |= 1u << static_cast<uint8_t>(rhs)
永远不能是常量表达式本身,因为它修改了lhs
.在 C++14 中禁止这样做的规则是 §5.19/2.15(C++11 中也存在一个有效等效的规则):
A conditional-expression
e
is a core constant expression unless the evaluation ofe
, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:
- modification of an object (5.17, 5.2.6, 5.3.2) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of
e
;
在 C++11 中,由于 §7.1.5/5,它必须是一个:
For a
constexpr
function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19), the program is ill-formed; no diagnostic required.
调用替换后不存在使返回表达式成为常量表达式的参数:赋值阻止了这种情况。因此,该程序在 C++11 中格式错误(但不需要诊断),并且在使用 -std=c++11
编译时, GCC 显示合规行为。
在 C++14 中,该规则被调整:
For a non-template, non-defaulted
constexpr
function […], if no argument values exist such that an invocation of the function […] could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.
这使得返回表达式本身成为一个非常量表达式,只要该函数在另一个核心常量表达式中是可计算的,例如来自另一个constexpr
功能:
constexpr auto foo(FooEnum rhs)
{
uint32_t x = 0;
x |= rhs;
return x;
}
foo(FooEnum::Foo1)
是核心常量表达式,因此 operator|=
可以在核心常量表达式中调用,因此函数定义是合式的。
正如 @dyp 在评论中指出的那样,GCC 仅支持“对 constexpr 函数的松弛约束”- 自版本 5 以来的功能。GCC 5.1 compiles your code .
现在 constexpr
的尸体函数通常由本身不是常量表达式的语句组成。第一个引用部分后面的示例显示了一个函数 incr
,GCC 也会拒绝:
constexpr int incr(int &n) { return ++n; } constexpr int h(int k) { int x = incr(k); // OK: incr(k) is not required to be a core // constant expression return x; }
关于c++ - clang 和 gcc 中的 Constexpr 复合赋值运算符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29900218/