为什么 GCC 编译的 constexpr 函数永远不会产生常量表达式?

问题描述 投票:0回答:4

(据我所知,使用的编译器是 gcc 和 c++17(在 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';
}

上面的代码在编译时出现错误:

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';
}

此代码在功能上是相同的,并且可以编译,即使增量仍然不是 constexpr。我不明白在 [0, 1) 范围内的 for 循环怎么可能导致编译器意识到函数

f
实际上是一个 constexpr。

如果有人可以对 c++ 中的 constexpr 以及这种明显的不一致给出一些见解,我将不胜感激。

c++ c++17 language-lawyer constexpr
4个回答
13
投票

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

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

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

请注意,如果

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

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


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) 非文字类型或静态或线程存储持续时间的变量的定义。

可以看出,

f

的定义没有违反列表中的任何要求。因此,如果编译器选择不对此进行诊断,则它是符合要求的。

正如

aschepler 的回答中指出的那样,constexpr

这样的函数不能在常量表达式中调用,但也不能诊断,被认为是格式错误的,无需诊断。

您实际上并没有在编译时“调用”

2
投票

如果您的主要功能包括:
f

我怀疑您会收到“f() 不是常量表达式”错误。

这是一个
相关问题

该标准要求

0
投票
函数实际上可以在编译时针对

some

 组参数进行评估,但不是全部。它不需要编译器来诊断 
constexpr 函数执行某些在某些情况下可能是非编译时的事情,甚至不需要诊断这样的函数是否具有这样一组参数。这避免了他们必须解决停机问题。

© www.soinside.com 2019 - 2024. All rights reserved.