我有以下代码:
的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标志编译它。
答案 0 :(得分:8)
表达式lhs |= 1u << static_cast<uint8_t>(rhs)
本身永远不能是常量表达式,因为它会修改lhs
。禁止在C ++ 14中使用的规则是§5.19/ 2.15(在C ++ 11中也存在一个有效的等效规则):
条件表达式
e
是核心常量表达式,除非e
的评估,遵循抽象机器的规则 (1.9),将评估以下表达式之一:
- 修改对象(5.17,5.2.6,5.3.2),除非应用它 指向非易失性的文字类型的非易失性左值 生命周期始于
的评估范围内的对象e
;
在C ++ 11中,由于§7.1.5/ 5:
,它必须是一个对于
constexpr
函数,如果不存在函数参数值,使得函数调用替换将产生常量表达式(5.19),则程序格式错误;无需诊断。
在调用替换之后,不存在使返回的表达式成为常量表达式的参数:赋值可以防止这种情况发生。因此,程序在C ++ 11中格式不正确(但不需要诊断),并且在使用-std=c++11
进行编译时,GCC显示符合规范的行为。
在C ++ 14中,该规则已经过调整:
对于非模板,非默认
constexpr
函数[...],如果 没有参数值这样函数的调用[...]可以是核心常量的一个被评估的子表达式 表达式(5.19),该程序格式不正确;无需诊断。
这使得返回表达式本身可以是非常量表达式,只要该函数在另一个核心常量表达式中是可评估的,例如,来自另一个constexpr
函数:
constexpr auto foo(FooEnum rhs)
{
uint32_t x = 0;
x |= rhs;
return x;
}
foo(FooEnum::Foo1)
是一个核心常量表达式,因此可以在核心常量表达式中调用operator|=
,因此函数定义格式正确。
正如评论中的@dyp所述,GCC仅支持自版本5以来的“对constexpr函数的放宽约束”特征。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; }