【问题标题】:Modifying a global variable in a constexpr function in C++17在 C++17 中修改 constexpr 函数中的全局变量
【发布时间】:2018-12-05 18:55:36
【问题描述】:

在 C++17 中,是否允许在 constexpr 函数中修改全局变量?

#include <iostream>

int global = 0;

constexpr int Foo(bool arg) {
    if (arg) {
        return 1;
    }
    return global++;
}

int main() {
    std::cout << global;
    Foo(true);
    std::cout << global;
    Foo(false);
    std::cout << global;
}

我不希望你能做到,但 clang 6 允许它:https://godbolt.org/g/UB8iK2

但是,GCC 没有:https://godbolt.org/g/ykAJMA

哪个编译器是正确的?

【问题讨论】:

  • @AnT • 我需要一个备忘单来保持 C++11、C++14 和 C++17 的约束。
  • 如果您强制编译时评估constexpr int a = Foo(false),则两者都不起作用,但我不确定哪个(如果有的话)不正确。
  • 对我来说这似乎是 gcc 中的一个基本错误
  • 肯定是gcc bug,提交86327
  • 有趣的是,clang 不允许将函数作为常量表达式评估为修改 global 的分支上的常量表达式(如 @user975989 所述),如果您删除if (arg) return 1; 分支将无法编译,尽管仍然满足@codekaiser 的答案中列出的要求。

标签: c++ language-lawyer c++17 constexpr


【解决方案1】:

哪个编译器是正确的?

Clang 是对的。

根据dcl.constexpr/3定义constexpr函数

constexpr 函数的定义应满足以下条件 要求:

(3.1) 其返回类型应为字面量类型;
(3.2) 它的每个参数类型都应该是文字类型;
(3.3) 其函数体应为= delete= default或复合语句 包含:

(3.3.1) 一个 asm 定义,
(3.3.2) goto 语句,
(3.3.3) 标识符标签,
(3.3.4) 一个 try 块,或
(3.3.5) 非文字类型或静态变量的定义或 线程存储持续时间或不执行初始化。

同样按照dcl.constexpr/5:

对于两者都不是的constexpr 函数或 constexpr 构造函数 默认也不是模板,如果不存在参数值 函数或构造函数的调用可以是评估的 核心常量表达式的子表达式,

Foo(true) 可以计算为一个核心常量表达式(即1)。

另外,Foo(false) 可以,但不需要不断评估。

结论

因此,GCC 中的 bug


非常感谢@Barry、@aschepler 和@BenVoigt 帮助我回答这个问题。

【讨论】:

  • 肯定更清楚了。我相信编译器实际上不可能推断global 的值,即使它将Foo(false) 内联到main 中,因为global 具有外部链接。 (输出不需要是0\n0\n1,因为另一个编译单元中的一些全局构造函数可能会改变global)因此,它的运行时副作用必须保留。
  • 虽然我相信你的解释,但这实际上可能是一个缺陷,或者至少是 GCC 家伙所喜欢的标准中的模棱两可。 dcl.constexpr/7 声明 "...与非 constexpr 相同的结果...除了可以出现在常量表达式中"。现在,Foo(false) 显然 不能 出现在常量表达式中,所以……这可能就是原因。
  • 您是否愿意以非标准的胡言乱语添加一些关于 OP 示例应该发生什么的解释?我倾向于说我们这里的主要是规范中的错误,而不是使其非法。但也许我还不够了解constexpr
  • @leftaroundabout,我会试试的。 global 可能会或可能不会在其他翻译单元中被修改,因为它的链接和它没有明确定义为const 程序员。如果编译器可以在编译时评估表达式,它会;但并不意味着它应该。因此,在我自己看来,有一个不严格的条款(可以但不是必需的)更好。另请参阅 Ben Voigt 的评论。
  • @Damon, true Foo(false) 不能出现在常量表达式中。支持您的陈述,在给出的代码中,它只是一个函数调用(不是常量表达式)。与 constexpr int i = Foo(false); 不同,这将使用 Clang 提示 diagnostic message
【解决方案2】:

我会补充说 dcl.constexpr/5 还需要:

对于既不是默认值也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值,则函数或构造函数的调用可以是核心常量表达式的求值子表达式,或者,对于构造函数,某些对象([basic.start.static])的常量初始化器,程序格式错误,不需要诊断。

由于您故意编写函数以使 Foo(true) 计算为核心常量表达式,因此不需要 Foo(false)

【讨论】:

    猜你喜欢
    • 2022-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-27
    • 1970-01-01
    • 1970-01-01
    • 2017-01-23
    相关资源
    最近更新 更多