【问题标题】:New-expression with consteval constructor in constexpr contextconstexpr 上下文中带有 consteval 构造函数的 New 表达式
【发布时间】:2022-12-21 02:38:24
【问题描述】:
struct A {       
    consteval A() {};
};

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

int main() {
    static_assert(g());
}

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

虽然,将 new A 替换为 new A() 会导致 GCC 也接受该程序(但也不接受 new A{})。


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

  1. consteval 替换为 constexpr

  2. constexpr 替换为 consteval

  3. 代替

    auto a = new A;
    delete a;
    

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

    A a;auto&amp;&amp; a = A{};A{};

    只有例外:

    • 带有 libstdc++ 的 Clang 主干似乎由于不相关的错误而无法使用 std::allocator 版本进行编译。对于 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
      
    • 只要构造函数上有 consteval,MSVC 就会拒绝 std::allocator 版本:

      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
              ]
      

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


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


    受到 this answer 下的 cmets 的激励。

【问题讨论】:

  • 分配器版本实际上并不初始化对象。我估计调用 construct 将表现得像裸露的 new 表达式。
  • 有趣的是,将 new A 更改为 new A() 让 GCC 对代码感到满意。
  • @SolomonUcko 这个 bug 似乎在几个版本前就已经修复了:godbolt.org/z/qcxhvefxv
  • 我不阅读标准论文来验证我的想法。但据我了解:consteval 必须仅在编译时上下文中使用。由于 constexpr 可以在编译时和运行时使用,它会拒绝 consteval 表达式。有趣的是,我像这样更改了 g 函数: constexpr bool g() { if constexpr( std::is_constant_evaluated() ) { auto a = new A;删除一个; } 返回真;但代码在 MSVC 17.3.5(C++ 最新版本)下仍然被拒绝。
  • @TeaAgeSolutions 不,对 consteval 函数的函数调用可以出现在任何地方(显式或隐式),但与上下文无关,调用本身必须形成常量表达式,假设它没有出现在另一个 consteval 函数中。这里的问题是隐式构造函数调用形成常量表达式意味着什么,以及它如何与 new-表达式语义交互。回到这个问题,我认为标准没有正确指定这一点,类似于它没有正确指定 constexpr 变量的行为。

标签: c++ language-lawyer c++20 constexpr consteval


【解决方案1】:

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

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

注意单词'或转换''隐式调用'- 这似乎暗示该规则旨在应用于每个函数调用的基础上。1个单个原子表达式的评估可以由多个这样的调用组成,例如在这种情况下这新表达式它可以调用分配函数、构造函数和释放函数。如果选择的构造函数是consteval,则求值的部分新表达式初始化对象(即构造函数调用),以及只要那部分是立即调用。根据这种解释,使用 newconsteval 构造函数不应该是格式错误的,无论上下文如何 - 即使在常量表达式之外 - 当然,只要对象的初始化本身是常量。

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

一种常量表达式是一个 glvalue 核心常量表达式,它指的是一个常量表达式(如下定义)的允许结果的实体,或者是一个 prvalue 核心常量表达式,其值满足以下约束 [...]

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

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


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

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

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

  • 当解释为常量表达式[...]

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

constinit A a;

但看不到固定的表情。


所以,回答你的问题:

g 的调用是否被评估为明显常量评估表达式的一部分并不重要3个不管你对 [expr.const]/13 的解释是什么。 new A 即使在正常评估期间也是格式正确的,或者在直接函数上下文之外的任何地方格式错误。

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


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

[2] Yes, a non-expression can be a subexpression.

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

【讨论】:

  • 这显然是一个错误“:我假设你在这里的意思是它一定是一个错误,因为编译器与自身不一致,而不是因为 new A() 绝对是错误的,对吧?
  • 正如我在重新访问该问题时在 cmets 中提到的那样,我得出了与您类似的结论。所以这对我来说很有意义。但是,我真的不明白为什么 GCC 会考虑通过 std::construct_at 进行新放置,这与分配新表达式中的对象构造不同。那部分对我来说尤其不一致。
  • 是的,我指的是 () 在这种情况下完全不同。 std::construct_at 被接受是同一个错误的表现 - 不带参数调用它相当于一个看起来像这样的新位置:new(...) A()。添加虚拟 int 参数也会导致它被拒绝。
  • 啊对了。 GCC 似乎专门针对值初始化做了一些不同的事情。
猜你喜欢
  • 1970-01-01
  • 2016-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-29
  • 2022-10-13
  • 2013-12-24
相关资源
最近更新 更多