更新了对标准的更准确参考:
我发现相关的部分(链接来自此处的 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&)
printf
code
f(A{A{}})
我的原始帖子供参考(有助于理解一些评论):
对我来说,输出诊断很有意义。我关于立即调用的心智模型是这样的:您只能在立即上下文中使用立即函数。包含除 constexpr 操作以外的任何内容的表达式不是直接上下文。
在您的示例中,表达式不仅是对 constexpr 构造函数的调用,而且由于临时是表达式的一部分,因此它的破坏也应该作为表达式评估的一部分发生。因此,您的表达不再是直接的上下文。
我只是在使用placement new 调用构造函数来避免dtor 调用成为表达式的一部分,但是placement new 本身也不被视为constexpr。我认为,在概念上最好用指针来解释,这根本不应该出现在直接的上下文中。
如果从表达式中删除 ctor/dtor:
A a;
a.f();
然后它编译得很好。
ICC 中有一个有趣的错误,A{}.f()
即使使用constexpr
dtor 也无法编译,无论您的定义多么微不足道,您都无法说服它f
:
error: call to consteval function "A::f" did not produce a valid constant expression
A{}.f();
^
虽然它编译了a.f()
上面列出的简单变体而没有任何抱怨。