【问题标题】:Where in C++14 Standard does it say that a non-constexpr function cannot be used in a definition of a constexpr function?在 C++14 标准中哪里说不能在 constexpr 函数的定义中使用非 constexpr 函数?
【发布时间】:2015-12-14 17:17:50
【问题描述】:

例如,除非incr() 被声明为constexpr,否则下面的代码不会编译:

int incr(int& n) {
    return ++n;
}

constexpr int foo() {
    int n = 0;
    incr(n);
    return n;
}

查看 C++14 中的第 7.1.5/3 节,我们有:

constexpr 函数的定义应满足以下条件 约束:
(3.1) — 它不能是虚拟的 (10.3);
(3.2) — 它的返回类型应为文字类型;
(3.3) — 它的每个参数类型都应该是文字类型;
(3.4) — 其函数体应为 = delete、= default 或不包含

的复合语句

(3.4.1) — asm 定义,
(3.4.2) — goto 语句,
(3.4.3) — 一个 try 块,或
(3.4.4) — 变量的定义 非文字类型或静态或线程存储持续时间或 不执行初始化。

【问题讨论】:

  • 可能值得注意的是previous question,尽管从不同的角度来看,它涵盖了很多相同的领域。
  • @ShafikYaghmour 非常感谢您的 cmets 提出这个新问题。
  • 请注意,您引用的内容是 necessary 但不是 sufficient 条件,即如果违反了这些条件,则代码格式不正确,但是,如果它们没有被违反,那么代码可能正确也可能不正确,我们必须查看规范的其他部分。

标签: c++ language-lawyer c++14 constexpr


【解决方案1】:

两段之后,在 [dcl.constexpr]/5 中:

对于非模板、非默认的constexpr 函数或非模板、非默认、非继承 constexpr 构造函数,如果不存在参数值,则调用函数或构造函数 可以是核心常量表达式 (5.20) 的求值子表达式,或者,对于构造函数,常量 某些对象的初始化程序(3.6.2),程序格式错误;无需诊断。

由于incr(),不存在可以使foo() 成为核心常量表达式的参数,因此程序是非良构(NDR)。

【讨论】:

  • 我认为 §7.1.5/5 与该问题无关。例如,在标准中的什么地方,是否说 constexpr 函数的主体必须是核心常量表达式?
  • @Ayrosa:因为不是。它甚至不是“表达式”,而是一系列语句。但是必须存在一条通过该主体的路径,该路径不涉及对任何不是核心常量表达式的表达式进行求值。
  • @Ayrosa 5.19 "条件表达式 e 是一个核心常量表达式,除非按照抽象机 (1.9) 的规则对 e 的求值将求出其中之一以下表达式: ... 一个函数的调用,而不是一个字面量类的 constexpr 构造函数,一个 constexpr 函数,或一个普通析构函数的隐式调用”。这里没有提到子表达式。
  • @Barry 我想我终于明白你在说什么了,而且似乎是正确的。但更准确地说,我会说:“由于incr(),即因为第 5.19/2 节( 2.2),因此程序格式错误。”。很好的答案 (+1)。
  • “因此程序格式错误”NDR。 NDR 部分很重要。
【解决方案2】:

您要查找的是 § 5.19:

conditional-expression e 是一个核心常量表达式,除非 e 的求值遵循抽象机 (1.9) 的规则, 将评估以下表达式之一:

这适用于作为constexpr 函数调用的表达式的求值。也就是说,调用constexpr 函数将是一个“核心常量表达式”,如果对函数求值,即根据 C++ 抽象机的规则执行函数体,并没有做任何在§ 5.19 中给出的列表。

列表中的一项是:

  • 调用非 [...] constexpr 函数的函数

因此,将其应用于您的示例:评估表达式 foo() 评估对函数 incr() 的调用,该函数不是 constexpr 函数,这意味着表达式 foo() 不是核心常量表达式。

此外,由于上述对于您的函数foo 的所有可能调用都是正确的,因此第 7.1.5/5 节中的规则开始生效并意味着您的示例程序格式错误,不需要诊断,即使您从来没有真正打电话给foo()


正如 Ben Voigt 指出的,一个 constexpr 函数可以包含对非 consexpr 函数的调用,只要该函数的特定评估实际上并不评估任何此类函数调用(或者它出现在不需要常量的上下文中表达式)。

5.19 中的限制仅与实际最终作为表达式评估的一部分进行评估的表达式有关。

例如:

#include <iostream>

int incr(int &n) { return ++n; }

enum E {be_constexpr, not_constexpr};

constexpr int foo(E e = be_constexpr) {
  int n = 0;
  if (e == not_constexpr) { incr(n); }
  return n;
}

int main() {
  constexpr int a = foo(); // foo() is a constant expression
  int b = foo(not_constexpr); // may or may not evaluate `foo(non_constexpr)` at runtime. In practice modern C++ compilers will do compile-time evaluation here even though they aren't required to.
  // constexpr int c = foo(not_constexpr); // Compile error because foo(not_constexpr) is not formally a constant expression, even though modern compilers can evaluate it at compile-time.

  std::cout << a << ' ' << b << '\n';
}

【讨论】:

  • 我花了一段时间才真正体会到你和 Ben Voigt (+1) 两个答案的质量。
【解决方案3】:

没有。

以下是允许的,即使它完全按照您的推测是被禁止的:

int incr(int& n) {
    return ++n;
}

constexpr int foo(bool x) {
    int n = 0;
    if (x) incr(n);
    return n;
}

您的问题中的代码被规则禁止,Barry 在他的回答中引用。但在我的变体中,确实存在一组参数(特别是 false),使用这些参数调用 foo 会导致编译时常量表达式。

请注意,不需要进行诊断 - 符合标准的编译器也可以允许您的版本编译。

【讨论】:

  • 值得注意的是,g++ 无法编译这两个版本,在两种情况下都会给出相同的(不正确的)错误消息。 clang++ 编译了这个并拒绝了原来的,引用了正确的原因:“constexpr 函数从不产生常量表达式”。
  • @n.m.该错误已在 gcc 主干中修复
  • 我花了一些时间才真正体会到你和 bames53 的两个答案的质量 (+1)。
猜你喜欢
  • 1970-01-01
  • 2013-12-24
  • 2016-10-05
  • 1970-01-01
  • 2018-10-29
  • 1970-01-01
  • 1970-01-01
  • 2014-03-14
  • 1970-01-01
相关资源
最近更新 更多