9
struct A {       
    ~A() {}
    consteval A() {}
    consteval auto f() {}
};

int main() {
    A{};
    //A{}.f(); //1
}

https://godbolt.org/z/4KPY5P7o7

该程序被 ICC、GCC 和 Clang 接受,但被 MSVC 拒绝,它抱怨析构函数不在constexpr立即函数调用中。

添加标记的行会//1导致所有四个编译器都拒绝该代码。


问题:无论哪种情况,编译器是否正确,如果正确,为什么?


请注意,这里有趣的部分是由于非平凡的析构函数A而导致的非文字部分。constexpr删除它的声明,所有编译器都接受带和不带的变体//1

constexpr/函数和常量表达式的非文字类型有一些特定的限制consteval,但我认为它们中的任何一个都不应该适用于此。限制是返回类型、参数类型、局部变量定义的类型、右值到左值的转换和对象的修改。我想只有最后一个可以在这里申请。但是[expr.const]/5.16中的修改究竟是什么意思,这里会修改哪个对象?

我还认为 MSVC 的抱怨是不正确的,因为对象的破坏不应该是其构造函数立即调用的一部分。

另请参阅我之前的启发这个问题的问题:Consteval constructor and member function calls in constexpr functions

4

1 回答 1

1

更新了对标准的更准确参考:

我发现相关的部分(链接来自此处的 N4868 草案):

  • 立即调用完整表达式 [ expr.const ]
  • “临时对象作为评估完整表达式 ([intro.execution]) 的最后一步被销毁,该完整表达式([intro.execution]) (词法上)包含它们被创建的点。......销毁临时对象的值计算和副作用是相关联的只有完整的表达式,而不是任何特定的子表达式。” [类.临时]
  • “参数列表是调用中的表达式列表,通过在规范化成员函数调用中添加 . 运算符的左操作数作为隐含对象参数 ([over.match.funcs]) 来扩充。” [over.call.func]
  • “一个常量表达式要么是一个glvalue核心常量表达式......,要么是一个prvalue核心常量表达式......” [expr.const]
  • “表达式 E 是一个核心常量表达式,除非 E 的评估,遵循抽象机 ([intro.execution]) 的规则,将评估以下之一:......非 constexpr 函数的调用;” [expr.const]
  • 立即调用应该是一个常量表达式。” [expr.const]
  • “一个对象或引用可以在常量表达式中使用,如果它是......一个非易失性的 const 限定字面量类型的临时对象,其生命周期被扩展 ([class.temporary]) 到可用于常量表达式的变量的生命周期” [expr.const]
  • “一个类型是文字类型,如果它是:......一个可能具有以下所有属性的 cv 限定类类型:它有一个 constexpr 析构函数([dcl.constexpr]),” [basic.types]

考虑以下示例:

struct A {       
  ~A() {} // not constexpr
  consteval int f() { return 1; }
};

template<class T>
consteval int f(T&& a) { return sizeof(a); }
consteval int f(int x) { return x; }
void g() {}

int main() {
  A a;
  f(a);           // ok
  a.f();          // ok
  f(a.f());       // ok
  f(sizeof(A{})); // ok

  f(A{});     // not ok TYPE 1 (msvc) or TYPE 2 (clang, gcc)
  A{}.f();    // not ok TYPE 1 (msvc) or TYPE 2 (clang, gcc)
  f((A{},2)); // not ok TYPE 1 (clang, msvc) or TYPE 2 (gcc)
  f((g(),2)); // not ok TYPE 1 (clang, gcc, icc, msvc)
}

错误诊断是关于违反立即调用应该是常量表达式。

// msvc:
error C7595: 'f' ((or 'A::f')): call to immediate function is not a constant expression
// icc:
call to consteval function "f(T&&) [with T=A]" ((or "A::f" or "f(int)")) did not produce a valid constant expression
// clang:
error: call to consteval function 'f<A>' ((or 'A::f' or 'f')) is not a constant expression

请注意,gcc 没有明确提及违反此 consteval/immediate 函数特定规则。

对于临时人员,我们从不同的编译器收到两种类型的诊断。有些人看到了在常量(完整)表达式中调用非 constexpr 析构函数或函数的问题。类型 1:

// msvc:
note: failure was caused by call of undefined function or one not declared 'constexpr'
note: see usage of 'A::~A' ((or 'g'))
// icc:
note: cannot call non-constexpr function "g"
// gcc:
error: call to non-'constexpr' function 'void g()'
// clang:
note: non-constexpr function '~A' ((or 'g')) cannot be used in a constant expression

其他人(除了 icc,它对此保持沉默)强调非文字类型的临时对象不能出现在常量表达式中。类型 2:

// gcc:
error: temporary of non-literal type 'A' in a constant expression
note: 'A' is not literal because:
note:   'A' does not have 'constexpr' destructor
// clang:
note: non-literal type 'A' cannot be used in a constant expression

我认为对于 consteval 的考虑A{}.f()等价于这种f(A{})情况,因为A::f.

icc 编译的Fedor出人意料的观察结果是真实的,即使实现调用 eg也是如此。编译,但不输出任何内容。我认为这是一个错误。有趣的是,icc 会为语义上非常相似的变体生成错误。A{A{}}.f()A::A(const A&)printfcodef(A{A{}})


我的原始帖子供参考(有助于理解一些评论):

对我来说,输出诊断很有意义。我关于立即调用的心智模型是这样的:您只能在立即上下文中使用立即函数。包含除 constexpr 操作以外的任何内容的表达式不是直接上下文。

在您的示例中,表达式不仅是对 constexpr 构造函数的调用,而且由于临时是表达式的一部分,因此它的破坏也应该作为表达式评估的一部分发生。因此,您的表达不再是直接的上下文。

我只是在使用placement new 调用构造函数来避免dtor 调用成为表达式的一部分,但是placement new 本身也不被视为constexpr。我认为,在概念上最好用指针来解释,这根本不应该出现在直接的上下文中。

如果从表达式中删除 ctor/dtor:

A a;
a.f();

然后它编译得很好。

ICC 中有一个有趣的错误,A{}.f()即使使用constexprdtor 也无法编译,无论您的定义多么微不足道,您都无法说服它f

error: call to consteval function "A::f" did not produce a valid constant expression
      A{}.f();
          ^

虽然它编译了a.f()上面列出的简单变体而没有任何抱怨。

于 2022-01-27T11:11:25.220 回答