什么决定了 constexpr 函数是否是常量表达式?

mch*_*l12 29 c++ constexpr c++17

(据我所知,使用的编译器是带有 c++17 的 gcc(在 Visual Studio 中很难找到))

#include <iostream>

using namespace std;

void increment( int& v )
{
    ++v;
}

int constexpr f()
{
    int v = 0;
    increment( v );
    return v;
}

int main( )
{
    cout << f( ) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

上面的代码给出了编译错误:

constexpr 函数 'f' 不能产生常量表达式。

据我了解,这是因为该函数increment不是 constexpr。让我困惑的是以下代码编译得很好:

#include <iostream>

using namespace std;

void increment( int& v )
{
    ++v;
}

int constexpr f()
{
    int v = 0;
    for( int i = 0; i < 1; ++i )
    {
        increment( v );
    }   
    return v;
}

int main( )
{
    cout << f( ) << '\n';
}
Run Code Online (Sandbox Code Playgroud)

这段代码在功能上是相同的,它确实可以编译,即使 increment 仍然不是 constexpr。我不明白通过范围 [0, 1) 的 for 循环如何导致编译器意识到该函数f实际上是一个 constexpr。

如果有人可以对 C++ 中的 constexpr 和这种明显的不一致提供一些见解,我将不胜感激。

asc*_*ler 12

根据[dcl.constexpr]/6,这两个程序都是“格式错误的,不需要诊断” :

对于既不是默认值也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值,使得函数或构造函数的调用可以是核心常量表达式的计算子表达式,或者对于构造函数,则为某些常量初始化对象([basic.start.static])的初始化完整表达式,程序格式错误,无需诊断。

gcc 只是没有注意到第二个程序的问题,这有点奇怪,但它仍然符合要求。

请注意,如果f在实际需要常量表达式的上下文中使用,则需要诊断,例如constexpr int n = f();

有些东西在 constexpr 函数中是不允许的。这些确实需要诊断(通常是错误消息),即使函数从未在常量表达式中使用 - 请参阅cigien 的回答。但问题中的程序不违反任何这些更严格的规则。

  • @cigien 添加了注释并链接到您的答案。 (2认同)

cig*_*ien 6

由于您不是f在常量表达式中调用,因此您的问题是询问编译器是否需要f仅根据定义来诊断无法在常量表达式中调用的内容。

这里列举了对函数定义的要求:constexpr

constexpr 函数的定义应满足以下要求:

(3.1) 其返回类型(如果有)应为文字类型;

(3.2) 它的每个参数类型都应该是一个文字类型;

(3.3) 不应是协程;

(3.4) 如果函数是构造函数或析构函数,则其类不应有任何虚拟基类;

(3.5) 其功能体不得包含

(3.5.1) goto 语句,

(3.5.2) 标识符标签,

(3.5.3) a definition of a variable of non-literal type or of static or thread storage duration.

As can be seen, the definition of f does not violate any of the requirements in the list. So a compiler is conforming if it chooses not to diagnose this.

As pointed out in aschepler's answer, constexpr functions like f that can't be called in a constant expression, but are not diagnosable as such, are considered ill-formed-no-diagnostic-required.