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

标签 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

编辑:
如果您想知道,为什么我首先尝试将运算符声明为 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 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.

调用替换后不存在使返回表达式成为常量表达式的参数:赋值阻止了这种情况。因此,该程序在 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/

相关文章:

c++ - 从文本文件中读取行并显示在屏幕上

c++ - 通过将基类强制转换为派生类并设置数据来扩展基类

c++ - 构造函数期望指向正在创建的实例的共享指针

c++ - 具有自定义数据结构的 C++ 中的运算符<<

c++ - 是否可以在可移植 C++03 代码中散列指针?

c++ - WinSock2 getaddrinfo 解析主机名

c++ - 我这里可以不使用线程同步吗?

c++ - 智能指针和参数列表分配规则

c++ - 比较悬挂指针是否合法?

c - 流错误指示器如何影响以下输入代码?