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

时间:2015-04-27 15:42:52

标签: c++ c++11 language-lawyer c++14 constexpr

我有以下代码:

的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
  3. 编辑:
    如果您想知道,为什么我首先尝试将运算符声明为constexpr,尽管示例中并不要求:
    在我的实际代码中,我使用|= - 运算符来实现(constexpr)| - 运算符,我想在constexpr表达式中使用它,但在此之前,我偶然发现了两个编译器之间的区别,没有意识到,gcc4.9并不完全支持c ++ 14(但是接受-std=c++14标志)。
    当使用运算符实际初始化一个全局constexpr变量时,即使clang只用c ++ 14标志编译它。

1 个答案:

答案 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;
}