constexpr 上下文中带有 consteval 构造函数的新表达式

use*_*522 35 c++ language-lawyer constexpr c++20 consteval

struct A {       
    consteval A() {};
};

constexpr bool g() {
    auto a = new A;
    delete a;
    return true;
}

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

https://godbolt.org/z/jsq35WxKs

GCC 和 MSVC 拒绝该程序,ICC 和 Clang 接受它:

///MSVC: 
<source>(6): error C7595: 'A::A': call to immediate function is not a constant expression
Compiler returned: 2

//GCC:
<source>: In function 'constexpr bool g()':
<source>:6:18: error: the value of '<anonymous>' is not usable in a constant expression
    6 |     auto a = new A;
      |                  ^
<source>:6:18: note: '<anonymous>' was not declared 'constexpr'
<source>:7:12: error: type '<type error>' argument given to 'delete', expected pointer
    7 |     delete a;
      |            ^
Compiler returned: 1

Run Code Online (Sandbox Code Playgroud)

尽管如此,替换new Anew A()GCC 也接受该程序(但new A{}两者都不是)。


至少进行以下一项更改会导致所有四个编译器接受该程序:

  1. consteval用。。。来代替constexpr

  2. constexpr用。。。来代替consteval

  3. 代替

    auto a = new A;
    delete a;
    
    Run Code Online (Sandbox Code Playgroud)

    auto alloc = std::allocator<A>{};
    auto a = alloc.allocate(1);
    std::construct_at(a);
    std::destroy_at(a);
    alloc.deallocate(a, 1);
    
    Run Code Online (Sandbox Code Playgroud)

    A a;,与auto&& a = A{};或与A{};

仅例外:

  • std::allocator使用 libstdc++ 的 Clang trunk 似乎由于不相关的错误而导致该版本编译失败。对于 Clang 13 或 libc++,它也被接受。

    In file included from <source>:1:
    In file included from [...]/memory:78:
    [...]/shared_ptr_atomic.h:459:14: error: missing 'typename' prior to dependent type name '_Atomic_count::pointer'
      static _Atomic_count::pointer
    
    Run Code Online (Sandbox Code Playgroud)
  • std::allocator只要consteval构造函数中有以下内容, MSVC 就会拒绝该版本:

    error C7595: 'A::A': call to immediate function is not a constant expression
    <source>(10): note: see reference to function template instantiation '_Ty *std::construct_at<_Ty,,void>(_Ty *const ) noexcept(false)' being compiled
            with
            [
                _Ty=A
            ]
    
    Run Code Online (Sandbox Code Playgroud)

替换static_assert(g());g()完全删除该调用似乎对这些结果没有任何影响。


哪些编译器是正确的,如果原始编译器格式不正确,为什么只不允许使用限定符和构造方法的特定组合?


受到此答案下评论的激励。

duc*_*uck 5

相关的写法是[expr.const]/13

如果表达式或转换是立即函数的潜在评估显式或隐式调用并且不在立即函数上下文中,则它是立即调用。立即调用应是常量表达式。

请注意“或转换”“隐式调用”这两个词- 这似乎意味着该规则旨在应用于每个函数调用。1单个原子表达式的求值可以由多个此类调用组成,例如new 表达式可以调用分配函数、构造函数和释放函数。如果选定的构造函数是,则初始化对象的new 表达式consteval的计算部分(构造函数调用)是立即调用。根据这种解释,使用withnewconsteval构造函数一起使用都不应该是格式错误的 - 即使在常量表达式之外 - 当然,只要对象的初始化本身是常量。

然而,这种阅读有一个问题:最后一句清楚地表明立即调用必须是表达式。如上所述的“亚原子调用”不是一个,它没有值类别,并且不可能满足常量表达式的定义([expr.const]/11):

常量表达式可以是泛左值核心常量表达式,它引用作为常量表达式(如下定义)允许的结果的实体,也可以是纯右值核心常量表达式,其值满足以下约束 [...]

此措辞的字面解释将排除在直接函数上下文之外使用构造函数consteval,因为对它的调用永远不会作为独立表达式出现。这显然不是预期的含义 - 除其他外,它会使标准库的部分内容无法使用。

此阅读的一个更乐观(但不太忠实于所写文字)的版本是,包含调用的原子表达式(形式上:调用是2的直接子表达式的表达式)必须是常量表达式。这仍然不允许您的new A构造,因为它本身不是常量表达式,并且在函数参数或一般变量的初始化等情况下也会留下一些不确定性。


我倾向于相信第一次阅读是预期的,这new A应该没问题,但显然存在实现分歧。

至于矛盾的“应是常量表达式”要求,这并不是标准中唯一出现这种情况的地方。在同一部分的前面,[expr.const]/2.2

变量或临时对象 o 被常量初始化,如果 [...]

  • 当解释为常量表达式时,其初始化的完整表达式是常量表达式[...]

显然,以下内容应该是有效的:

constinit A a;
Run Code Online (Sandbox Code Playgroud)

但看不到持续的表情。


所以,回答你的问题:

无论您采用哪种 [expr.const]/13 解释,对 的调用g是否作为明显常量计算表达式的一部分进行计算并不重要3 。new A即使在正常评估期间也是格式良好的,或者在直接函数上下文之外的任何地方格式错误。

从表面上看,Clang 和 ICC 执行的是前一套规则,而 GCC 和 MSVC 则遵循后者。除了 GCC 接受new A()异常值(这显然是一个错误)之外,两者都没有错,只是措辞有缺陷。


[1] CWG2410修复了措辞,以正确包含构造函数调用(既不是表达式也不是转换)之类的内容。

[2]是的,非表达式可以是子表达式。

[3] 这样的要求是不可能执行的。