clang 和 gcc 中的 Constexpr 复合赋值运算符
Constexpr compound assignment operator in clang and gcc
我有以下代码:
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 编译时,它抛出一个 error 说这不是 常量表达式:
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
,尽管示例中不需要这样做:
在我的实际代码中,我使用 |=
-operator 来实现 (constexpr) |
-operator,我想在 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 of e
, 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.
不存在使 returned 表达式在调用替换后成为常量表达式的参数:赋值阻止了这种情况。因此该程序在 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.
这使得 return 表达式本身成为一个非常量表达式,只要该函数在另一个核心常量表达式中是可计算的,例如来自另一个 constexpr
函数:
constexpr auto foo(FooEnum rhs)
{
uint32_t x = 0;
x |= rhs;
return x;
}
foo(FooEnum::Foo1)
是一个核心常量表达式,因此operator|=
可以在核心常量表达式中调用,因此函数定义是合式的。
正如@dyp 在评论中指出的那样,GCC 自第 5 版以来仅支持 "relaxing constraints on constexpr functions"-功能。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;
}
我有以下代码:
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 编译时,它抛出一个 error 说这不是 常量表达式:
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
,尽管示例中不需要这样做:
在我的实际代码中,我使用 |=
-operator 来实现 (constexpr) |
-operator,我想在 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.
不存在使 returned 表达式在调用替换后成为常量表达式的参数:赋值阻止了这种情况。因此该程序在 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.
这使得 return 表达式本身成为一个非常量表达式,只要该函数在另一个核心常量表达式中是可计算的,例如来自另一个 constexpr
函数:
constexpr auto foo(FooEnum rhs)
{
uint32_t x = 0;
x |= rhs;
return x;
}
foo(FooEnum::Foo1)
是一个核心常量表达式,因此operator|=
可以在核心常量表达式中调用,因此函数定义是合式的。
正如@dyp 在评论中指出的那样,GCC 自第 5 版以来仅支持 "relaxing constraints on constexpr functions"-功能。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; }