Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I got this example from §5.19/2 in N4140:

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

As far as I can tell, this is not a constexpr function. But the snippet compiles in clang and g++. See live example. What am I missing here?

like image 512
Ayrosa Avatar asked Dec 10 '15 21:12

Ayrosa


2 Answers

In C++14 the rules for constexpr function were relaxed and the paper N3597: Relaxing constraints on constexpr functions. The paper goes into the rationale and the effects and it includes the following (emphasis mine):

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.

and:

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).

and we can find this covered in N4140 section 7.1.5 [dcl.constexpr] which says:

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.

The last example shows how incr can be used in a constexpr:

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)

and the rule that covers the lifetime of k begins inside h(1) is:

  • 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;

The wording in 7.1.5 [dcl.constexpr] shows us why incr is a valid 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.

As the modified example given by T.C.:

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

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

shows, we can indeed use incr as a subexpression of a core constant expression and therefore it is not ill-formed.

like image 168
Shafik Yaghmour Avatar answered Oct 23 '22 01:10

Shafik Yaghmour


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

Why do you say that? The example from §5.19/2 reads:

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) not being a core constant expression does not mean incr cannot not be a constexpr function.

Under C++14's constexpr rules, it is possible to use incr in a constexpr context, for example:

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

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

Unless it's downright impossible for the body of the function to be constexpr (for example, calling a non-constexpr function unconditionally), the compiler has no reason to produce an error at the point of definition.

A constexpr function may even contain paths/branches in the body which would not be constexpr. As long as they are never taken in a constexpr context, you will not get an error. For example:

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

like image 40
melak47 Avatar answered Oct 23 '22 01:10

melak47