constexpr 函数中的 Consteval 构造函数和成员函数调用

use*_*522 5 c++ language-lawyer c++20 consteval

struct A {       
    int i;
    consteval A() { i = 2; };
    consteval void f() { i = 3; }
};

constexpr bool g() {
    A a;
    a.f();
    return true;
}

int main() {
    static_assert(g());
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/hafcab7Ga

该计划被 GCC、Clang、MSVC 和 ICC 全部拒绝,取而代之的是constexpr所有g四个consteval机构都接受它。

然而,当删除 call 时a.f();,仍然带有constexpron g,只有 ICC 仍然拒绝该代码。其他三人现在也都接受了。

我不明白为什么会这样。我的理解是,如果没有constevalon g,表达式a.f()不在直接函数上下文中,这将导致成员函数调用本身被评估为单独的常量表达式,然后无法修改成员,i因为成员的生命周期在评估期间没有开始那个常数表达式。

但为什么构造函数可以在相同的上下文中对同一个对象执行相同的操作呢?a的生命周期是否被认为是在 consteval 构造函数求值期间开始的?


另请注意, 的存在static_assert不会影响这些结果。constexpr从那时起完全删除g也不会改变编译器行为的任何内容。


正如 @Enlico 所指出的,即使在除 ICC 接受代码之外的所有编译器中用 on替换两者,A a;尽管根据我的理解,该表达式应该导致对立即构造函数调用和直接成员函数的两个单独的常量表达式进行求值调用。我认为后一个调用的行为应该与 完全相同,这使得这更加令人困惑。a.f();A{}.f();constexprga.f();

(读完@Barry的答案后,我现在意识到最后一句话没有任何意义。更正:A{}将是构造函数立即调用的一个常量表达式,A{}.f()作为一个整体将是成员函数立即调用的第二个常量表达式。这与表达方式明显不同a.f()。)

Bar*_*rry 8

规则是来自[expr.const]/13

如果表达式或转换可能被计算并且其最内部的非块作用域是立即函数的函数参数作用域,则该表达式或转换位于立即函数上下文中。如果表达式或转换是立即函数的潜在评估显式或隐式调用并且不在立即函数上下文中,则它是立即调用。立即调用应是常量表达式。

其中,立即数函数只是(来自[dcl.constexpr]/2)的术语:

用说明符声明的函数或构造函数consteval称为立即函数

从例子来看:

struct A {       
    int i;
    consteval A() { i = 2; };
    consteval void f() { i = 3; }
};

constexpr bool g() {
    A a;
    a.f();
    return true;
}
Run Code Online (Sandbox Code Playgroud)

该调用a.f()是立即调用(我们正在调用立即函数,但我们不在立即函数上下文中,g不是constexprconsteval,因此它必须是常量表达式。

它本身必须是一个常量表达式。不是整个调用g(),只是a.f()

是吗?No.通过写入 来a.f()改变,这违反了[expr.const]/5.16。作为常量表达式的限制之一是不能:aa.i

对象的修改([expr.ass]、[expr.post.incr]、[expr.pre.incr]),除非它应用于引用其非易失性对象的文字类型的非易失性左值生命周期开始于评估E

我们的对象a.i并不是在该表达式的求值过程中开始其生命周期的。因此,a.f()不是常量表达式,因此所有编译器都可以正确拒绝。

有人指出,这A().f();很好,因为现在我们在那里遇到了异常 -A()在评估该表达式期间开始了它的生命周期,所以A().i也这样做了,因此分配给它是可以的。

您可以将其视为A()常量评估器“已知”的含义,这意味着这样做A().i = 3;完全没问题。同时,a未知 - 所以我们不能这样做,a.i = 3;因为我们不知道a是什么。


如果g()是一个consteval函数,则 thea.f()将不再是立即调用,因此我们不再要求它本身是一个常量表达式。现在唯一的要求是它g()是一个常量表达式。

并且,当计算g()为常量表达式时, 的声明A a;现在位于表达式的计算范围内,因此a.f()不会阻止g()成为常量表达式。


规则上的差异是因为函数consteval需要在编译时调用,而函数仍然可以在运行时调用。constexpr