据我所知,下面的函数不是 constexpr,而是用 clang 和 g++ 编译的代码。我错过了什么?

As far as I can tell the function below is not constexpr, but the code compiles in clang and g++. What am I missing?

我从 N4140 的 §5.19/2 中得到这个例子:

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

据我所知,这不是 constexpr 函数。但是这段代码是用 clang 和 g++ 编译的。参见 live example。我在这里错过了什么?

As far as I can tell, this is not a constexpr function.

为什么这么说? §5.19/2 中的示例显示为:

constexpr int g(int k) {
    constexpr int x = incr(k); // error: incr(k) is not a core constant
                               // expression because lifetime of k
                               // began outside the expression incr(k)
    return x;
}

incr(k) 不是 核心常量表达式 并不意味着 incr 不能是 constexpr 函数。

根据 C++14 的 constexpr 规则,可以在 constexpr 上下文中使用 incr,例如:

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

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

除非函数体完全不可能是 constexpr(例如,无条件调用非 constexpr 函数),否则编译器没有理由在定义点产生错误。

constexpr 函数甚至可以在主体中包含 paths/branches 而不是 constexpr。只要它们从未在 constexpr context 中使用,您就不会收到错误。 例如:

constexpr int maybe_constexpr(bool choice, const int& a, const int& b) {
    return choice ? a : b;
}

constexpr int a = 0;
int b = 1;
static_assert(maybe_constexpr(true, a, b) == 0, "!");

live example

在 C++14 中,constexpr 函数的规则放宽了,论文 N3597: Relaxing constraints on constexpr functions。该论文探讨了基本原理和效果,包括以下内容(强调我的):

As in C++11, the constexpr keyword is used to mark functions which the implementation is required to evaluate during translation, if they are used from a context where a constant expression is required. Any valid C++ code is permitted in constexpr functions, including the creation and modification of local variables, and almost all statements, with the restriction that it must be possible for a constexpr function to be used from within a constant expression. A constant expression may still have side-effects which are local to the evaluation and its result.

和:

A handful of syntactic restrictions on constexpr functions are retained:

  • asm-declarations are not permitted.
  • try-blocks and function-try-blocks are not permitted.
  • Declarations of variables with static and thread storage duration have some restrictions (see below).

我们可以在 N4140 部分 7.1.5 [dcl.constexpr] 中找到它,它说:

The definition of a constexpr function shall satisfy the following constraints:

  • it shall not be virtual (10.3);

  • its return type shall be a literal type;

  • each of its parameter types shall be a literal type;

  • its function-body shall be = delete, = default, or a compound-statement that does not contain

    • an asm-definition,

    • a goto statement,

    • a try-block, or

    • a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.

最后一个示例显示了如何在 constexpr 中使用 incr

constexpr int h(int k) {
  int x = incr(k); // OK: incr(k) is not required to be a core
                   // constant expression
  return x;
}

constexpr int y = h(1); // OK: initializes y with the value 2
                        // h(1) is a core constant expression because
                        // the lifetime of k begins inside h(1)

覆盖the lifetime of k begins inside h(1)的规则是:

  • modification of an object (5.17, 5.2.6, 5.3.2) unless it is applied to a non-volatile lvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

7.1.5[dcl.constexpr]中的措辞向我们展示了为什么incr是一个有效的constexpr:

For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting constexpr constructor, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no diagnostic required.

如T.C修改后的例子:

constexpr int& as_lvalue(int&& i){ return i; }

constexpr int x = incr(as_lvalue(1)) ;

表明,我们确实可以使用 incr 作为核心常量表达式的子表达式,因此它不是病式的。