非 constexpr 调用的 constexpr 结果

dtl*_*rek 8 c++ constexpr c++11 constexpr-function

最近,我很惊讶以下代码也在 clang、gcc 和 msvc 中编译(至少在它们当前的版本中)。

struct A {
    static const int value = 42;
};

constexpr int f(A a) { return a.value; }

void g() {
    A a;  // Intentionally non-constexpr.
    constexpr int kInt = f(a);
}
Run Code Online (Sandbox Code Playgroud)

我的理解是,调用f不是 constexpr 因为参数i不是,但看来我错了。这是适当的标准支持的代码还是某种编译器扩展?

use*_*522 6

正如注释中提到的,常量表达式的规则通常不要求表达式中提到的每个变量以及其生命周期开始于表达式求值之外的每个变量都是constexpr

一个(长)要求列表,如果不满足这些要求,则会阻止表达式成为常量表达式。只要不违反任何一个,该表达式就是常量表达式。

使用的变量/对象的要求constexpr正式称为可在常量表达式中使用的对象(尽管确切的定义包含更详细的要求和例外,另请参阅链接的 cppreference 页面)。

查看列表,您可以看到仅在某些情况下才需要此属性,即仅对于其生命周期在表达式之外开始的变量/对象,并且如果对其执行虚拟函数调用,则会执行左值到右值的转换或者它是表达式中命名的引用变量。

这两种情况都不适用于此。不涉及虚函数,a也不是引用变量。通常,左值到右值的转换会导致需求变得重要。每当您尝试使用存储在对象或其子对象之一中的值时,就会发生左值到右值的转换。然而A,它是一个没有任何状态的空类,因此没有任何可读价值。当传递a给函数时,会调用隐式复制构造函数来构造 的参数f,但由于该类是空的,因此它实际上不执行任何操作。它不访问 的任何状态a

请注意,如上所述,如果您使用引用,则规则会更严格,例如

A a;
A& ar = a;
constexpr int kInt = f(ar);
Run Code Online (Sandbox Code Playgroud)

将会失败,因为ar命名了一个在常量表达式中不可用的引用变量。希望这个问题能够尽快得到解决,以更加一致。(参见https://github.com/cplusplus/papers/issues/973