10

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?

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Ayrosa
  • 3,385
  • 1
  • 18
  • 29
  • I'm not sure, but doesn't `constexpr`, for functions, require only one case for which it can be optimized to something at compile time? – CinchBlue Dec 10 '15 at 21:49
  • I get a compile error if I try to *use* `incr()` to initialize a `constexpr` variable. – Joel Cornett Dec 10 '15 at 22:01

2 Answers2

9

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • I'm not sure I understand what your problem with the current wording is. – T.C. Dec 11 '15 at 05:20
  • @T.C. well, `incr` is not a *subexpression* of `h(1)` and I don't see a case where `incr` could be a subexpression of a core constant expression and still be valid. Am I missing something here? – Shafik Yaghmour Dec 11 '15 at 10:36
  • @ShafikYaghmour I'm trying to understand your answer. It seems to me you're saying that the function `incr()` is not `constexpr` because it doesn't satisfy §7.1.5/5, but no diagnostic is required. So in a sense, clang and g++ are compliant. Is this correct? – Ayrosa Dec 11 '15 at 11:34
  • 1) I think you are probably reading *subexpression* too narrowly. 2) In any event, given `constexpr int& as_lvalue(int&& i){ return i; }`, `incr(as_lvalue(1))` is a core constant expression. – T.C. Dec 11 '15 at 11:35
  • @T.C. Correct me if I'm wrong, but I think you mean the following [snippet](http://coliru.stacked-crooked.com/a/bc06dc1ab370b6ce). It compiles in clang and g++, but in this case the evaluation of the function `incr()` is **not** a subexpression of any expression. So it still doesn't satisfy §7.1.5/5. – Ayrosa Dec 11 '15 at 12:34
  • @T.C. as far as I can tell in all other cases subexpression in used in a context with respect to a full-expression. So this seems like a different meaning. Although subexpression is never defined. – Shafik Yaghmour Dec 11 '15 at 12:41
  • @Ayrosa that example requires a little tweaking but it can work, see my modified answer. – Shafik Yaghmour Dec 11 '15 at 12:44
  • @ShafikYaghmour The `i` inside the function `as_lvalue` is a named-rvalue, and so an lvalue. Thus, the function `as_lvalue()` is returning a reference to a local variable in the function. Isn't that undefined behavior? – Ayrosa Dec 11 '15 at 13:29
  • 1
    @Ayrosa, `i` is already a reference. The function returns a reference to whatever is passed in. As long as the return value is not used after the passed in temporary is destroyed, it's fine. – chris Dec 11 '15 at 16:15
  • @melak47 after some good comments, I updated my answer. I believe the updated answer explains the details. – Shafik Yaghmour Dec 11 '15 at 16:40
  • I'm still not sure what `incr(k) is not required to be a core constant expression` means exactly - there is a reference to plain *constant expression* in §5.19/4: `A constant expression is either a glvalue core constant expression whose value refers to an object with static storage duration or to a function` - but `incr(k)` and `k` do not refer to values with static storage duration, unless I misunderstand? – melak47 Dec 11 '15 at 17:12
  • @chris I don't agree with you. The prvalue 1 passed to the function `as_lvalue()` is "moved" into the parameter `i` (an lvalue) which is destroyed just after the `return` statement is executed inside the function, when the stack is unwound. So, at the time the function `incr()` receives the argument returned by `as_lvalue()` the lvalue reference `n`, argument of `incr()` already refers to an undefined value. See also these two answers in SO: [1](http://stackoverflow.com/a/26321330/411165) and [2](http://stackoverflow.com/a/8808865/411165). – Ayrosa Dec 11 '15 at 18:32
  • @chris That is, the result of the expression `incr(as_lvalue(1)) ;` is undefined. – Ayrosa Dec 11 '15 at 18:32
  • 1
    @Ayrosa, Both of those return references to the local object, though. Here, `i` is a reference, not an object. You cannot refer to references, and can therefore not return a reference to `i`. It's similar to `int foo = 5; int& r = foo; int& r2 = r;`. The last statement makes `r2` a reference to `foo` rather than `r`, and `int& & r2 = r;` is ill-formed. – chris Dec 11 '15 at 18:42
  • 2
    @ShafikYaghmour After some thinking about this, I finally understood that `constexpr int x = incr(as_lvalue(1)) ;` proves that `constexpr int incr(int &n) { return ++n; }` is really a constexpr function (+1). – Ayrosa Dec 13 '15 at 12:56
  • @Ayrosa I find constant expression rules really do take some thinking about to really wrap your head around them. Every time I answer an interesting constant expression question I gain some new insight that I missed before and it does not help that it has evolved a lot since C++11. The odr rules are another area that require some deep thought someimes. – Shafik Yaghmour Dec 13 '15 at 23:46
7

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

melak47
  • 4,772
  • 2
  • 26
  • 37
  • Why did I think `incr()` is not `constexpr`? Because its argument is not `const`, and also, it can't be `const`. – Ayrosa Dec 10 '15 at 22:23
  • And more: the function `ìncr()` has side effects, it changes the value of its argument. – Ayrosa Dec 10 '15 at 22:45
  • ...which is permissible under C++14's *relaxed constexpr* rules – melak47 Dec 10 '15 at 22:46
  • In C++14 constexpr is somewhat orthogonal to const, unlike in C++11. I.e. they changed it so that constexpr member functions are not implied const, and also, you can have examples like melak47 showed where a variable which is not declared const is declared inside a constexpr function and manipulated – Chris Beck Dec 10 '15 at 22:47
  • I found this in Stroustrup's book (TCPL 4th edition), on its page 312: "A constexpr function cannot have side effects, so writing to nonlocal objects is not possible" – Ayrosa Dec 10 '15 at 23:14
  • @Ayrosa, As far as I know, that book covers C++11 and not C++14. It might contain a light section on C++14, but the book was primarily written for C++11, and was published in 2013. – chris Dec 10 '15 at 23:17
  • @chris This code also doesn't compile: `constexpr int incr(int &n) { return ++n; } int main() { int a = 10; constexpr int i = incr(a); }` – Ayrosa Dec 10 '15 at 23:20
  • @Ayrosa, And that's a good thing because `incr` isn't pure. Adapting from my (deleted) comment below, the important thing is not letting side effects escape the `constexpr` boundary. In this example, `foo` is pure, and while `incr` is not, calling it directly from main will not produce a constant expression. Allowing such side effects allows a wider range of procedural code to appear in a compile-time calculation without compromising purity since impure uses will not compile. – chris Dec 10 '15 at 23:27
  • @melak47 `...which is permissible under C++14's relaxed constexpr rules` Where in C++14 does the Standard say that side effects are now allowed in constexpr functions? – Leon Dec 10 '15 at 23:54
  • @LeonTrotski Unfortunately, I find these sections of the standard quite hard to grok (§5.19 *Constant expressions*, §7.1.5/3 *The definition of a constexpr function shall satisfy the following constraints: [...]* ), since in both cases the constraints are mostly expressed as "anything, except [*long list of exceptions, sometimes with exceptions to those exceptions*]". – melak47 Dec 11 '15 at 00:27
  • @LeonTrotski Basically though: §7.1.5/3.4: *its function-body shall be [...], or a compound-statement that does not contain* 3.4.1: *a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.*. So, a defintion of a non-static, non thread_local variable which *is* of literal type and *is* initialized is permitted, and it's not required to be `const` or `constexpr`. Furthermore, §7.1.5/3.3: *each of its parameter types shall be a literal type;* - so a reference parameter is permitted also. – melak47 Dec 11 '15 at 00:33
  • Then, onto §5.19/2: *A conditional-expression e is a core constant expression unless the evaluation of e [...] would evaluate one of the following expressions: [...]* §5.19/2.15: *modification of an object [...] **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**;*. Unfortunately I can't quite tie the bow on the lifetime constraint, I think *converted expressions* may be involved. – melak47 Dec 11 '15 at 00:45
  • However, §5.19/2 and §7.1.5/3.4.4 contain multiple examples of constexpr functions that modify local variables, I hope that's good enough :) – melak47 Dec 11 '15 at 00:48