(据我所知,使用的编译器是 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 以及这种明显的不一致给出一些见解,我将不胜感激。
这两个程序都是“格式错误,无需诊断”,根据 [dcl.constexpr]/6:
对于既不是默认值也不是模板的 constexpr 函数或 constexpr 构造函数,如果不存在参数值,则函数或构造函数的调用可以是核心常量表达式的计算子表达式,或者对于构造函数,可以是计算子表达式某些常量初始化对象的初始化完整表达式 ([basic.start.static]),该程序格式错误,无需诊断。
有点奇怪的是,gcc 没有注意到第二个程序的问题,但它仍然符合要求。
请注意,如果
f
在实际需要常量表达式的上下文中使用,则需要进行诊断,例如 constexpr int n = f();
。
有些事情在 constexpr 函数中是绝对不允许的。这些确实需要诊断(通常是错误消息),即使该函数从未在常量表达式中使用 - 请参阅 cigien 的回答。但问题中的程序并不违反任何这些更严格的规则。
由于您没有在常量表达式中调用
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
这样的函数不能在常量表达式中调用,但也不能诊断,被认为是格式错误的,无需诊断。您实际上并没有在编译时“调用”
some
组参数进行评估,但不是全部。它不需要编译器来诊断
constexpr
函数执行某些在某些情况下可能是非编译时的事情,甚至不需要诊断这样的函数是否具有这样一组参数。这避免了他们必须解决停机问题。