clang和gcc中的constexpr复合赋值运算符

7

I have the following code:

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没有区别)。

所以我的问题是:

  1. 哪个编译器是正确的(为什么)?
  2. 是否有一种实现operator | =的方法,使得g ++将其接受为constexpr

编辑:
如果你想知道,为什么我首先尝试声明运算符为constexpr,尽管在示例中不需要:
在我的实际代码中,我正在使��|=-运算符来实现(constexpr)|-运算符,我希望它能在constexpr表达式中使用,但在那之前,我遇到了两个编译器之间的差异,没有意识到gcc4.9并不完全支持c ++ 14(但接受-std=c++14标志)。
当使用运算符实际初始化全局constexpr变量时,即使clang也只能使用c ++ 14标志编译它。


“operator|=”本质上不是const吗? - Barry
@Barry:作为成员函数是的,但这是一个自由函数。而且,constexpr 和成员函数的常量性是两个不同的概念。 - MikeMB
2
在C++11常量表达式中不允许赋值操作。 (在C++11 [expr.const] / 2中明确指出,在常量表达式内禁止使用赋值和复合赋值。) - dyp
@dyp:谢谢,我不知道,但对于“-std=c++14”也是一样的。 - MikeMB
"放宽对constexpr函数的要求"仅受自gcc 5支持。链接的文件包括允许在常量表达式内进行赋值的内容。 - dyp
1个回答

8
表达式lhs |= 1u << static_cast<uint8_t>(rhs)本身永远不可能是一个常量表达式,因为它修改了lhs。禁止此类操作的规则在C++14中为§5.19/2.15(在C++11中也有等效的规则):
条件表达式e是核心常量表达式,除非按照抽象机器的规则(1.9),对e进行评估将产生以下表达式之一:
- 对对象的修改(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只支持“放宽对constexpr函数限制”的功能自5版本以来。 GCC 5.1编译您的代码

因此,现在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;
}

我理解在我的例子中,我的运算符没有在常量表达式中使用,这可能是clang即使在c++11模式下也接受它并忽略constexpr声明的原因。我不太明白“程序无效;无需诊断。”的意思是什么,这是否意味着在c++11模式下编译此代码是未定义行为的一个实例,还是例如实现定义?基本上,我想知道是否保证要么得到编译时错误,要么得到预期的行为(忽略constexpr,但bar在最后具有正确的值且没有奇怪的副作用)。 - MikeMB
@MikeMB Clang允许在C++11模式下编译它,因为它不需要诊断您的错误。也就是说,这大致相当于未定义行为。 - Columbo
“一个有效等价的”可能有点牵强,因为C++11禁止不加区分地进行赋值。但是,在这里它确实具有相同的效果。(嗯,我开始吹毛求疵了) - dyp
@dyp 随便修改,我现在太懒了:D - Columbo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接