12

while browsing one of my old questions on constexpr I stumbled onto a very(IMHO) important comment. Basically it boils down to : (this is legal C++11 :( )

 constexpr double f(bool b)
 {
 return b? 42:42/(rand()+1); // how pure is rand ;)
 }

My question is what is the reason this is allowed by the standard. Since Im a big fan of referential transparency I hope they have a good reason :) and I would like to know it.

BTW there is related Q but most of the A even dont mention pure thing, or when they do they dont specify the reasoning why std allows this. Relation between constexpr and pure functions

Community
  • 1
  • 1
NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277

3 Answers3

9

In the standard, the relevant requirement is buried below the main list of requirements for constexpr functions. It's in §7.1.5/5:

For a constexpr function, if no function argument values exist such that the function invocation substitution would produce a constant expression (5.19), the program is ill-formed; no diagnostic required.

§5.19 defines the requirements for constant expressions, such that you can't call rand().

The relaxed restriction allows you to have functions that are conditionally pure. Your example f(true) is a valid template argument, but f(false) is not.

The downside, of course, is that the compiler won't verify that a constexpr function can actually be used for its intended purpose. You need to write test cases.

Ah, litb's answer is also correct. (But this one is worded more simply.)

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 1
    It also a better answer because it quotes the relevant section of the standard as well as giving a good explanation of what it means. – Omnifarious Jan 23 '13 at 04:54
7

The keyword constexpr in the function definition tells the compiler that this function may be executed at compile-time if the all the arguments and variables are known at the compile-time itself. There is no such guarantee, though, for example when some of the values can be known only at runtime in which case the function will be executed at runtime.

However, it has nothing to do with pure or impure, since these terms imply that the output depends on the inputs only, and no matter how many times you call the function with the same values of input parameters, the output will be same everytime, irrespective of whether it is computed at compile-time or runtime.

Example,

constexpr int add(int a, int b) { return a + b; } //pure!

const int a = 2, b = 3; //const
int c = 2, d = 3;       //non-const

//we may read update c and d here!

const int v1 = add(2,3);  //computed at compile-time
const int v2 = add(a,3);  //computed at compile-time
const int v3 = add(2,b);  //computed at compile-time
const int v4 = add(a,b);  //computed at compile-time

const int v3 = add(c,3);  //computed at runtime
const int v3 = add(c,b);  //computed at runtime
const int v3 = add(a,d);  //computed at runtime
const int v3 = add(c,d);  //computed at runtime

Note that here add is a pure function irrespective of whether it is computed at compile-time or runtime.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    Any pure function may be evaluated at compile time under the as-if rule, regardless of `constexpr`. It's called constant folding. The `constexpr` in your example doesn't appear to make any difference at all. – Potatoswatter Jan 23 '13 at 04:02
  • 1
    @Potatoswatter: You mean we can write `std::array a;` even if `add` is not `constexpr`? I don't think so. – Nawaz Jan 23 '13 at 04:54
  • 1
    I meant in your example :v) . (Your answer doesn't seem to address constant expression contexts at all.) I [opened a question](http://stackoverflow.com/questions/14472359/why-do-we-need-to-mark-functions-as-constexpr?lq=1) about whether `std::array` would be a bad thing. – Potatoswatter Jan 23 '13 at 05:00
  • 1
    @Potatoswatter: Actually I kept the example simple, otherwise `constexpr` imposes lots of restrictions on the function-body which I didn't want to discuss in this answer, as it seems not to be point of this question (?). – Nawaz Jan 23 '13 at 05:02
  • 1
    Well, just mentioning that `std::array` is essentially different from what you had already mentioned before. However, the `constexpr` in the example doesn't do anything, since there are no constant expression contexts in it. It comes down to constant propagation with or without `constexpr`. – Potatoswatter Jan 23 '13 at 05:03
  • 1
    @JohannesSchaub-litb: Should it be `constexpr` instead of `const`? then it will be computed at compile-time? – Nawaz Jan 23 '13 at 17:12
  • 1
    Here is what I think happens, although I have not checked in the spec deep enough to be sure: I believe it is unspecified whether or not they are computed at compile time. If they are, this code using `v1` is valid: `constexpr int a = v1;` if they are not, then that code using `v1` is invalid. If `v1` had been defined as `constexpr int v1 = add(2,3);`, then it would definitely be computed at compile time. The difference here is that `const` does not require constant expression evaluation, while `constexpr` requires it. – Johannes Schaub - litb Jan 23 '13 at 17:19
  • 1
    Ok having thought about it a bit more, I believe it is guaranteed to work. The condition to use `v1` in a constant expression is that it is an *object with a preceding initialization, initialized with a constant expression*. So for an implementation to comply with this, it has to do constexpr evaluation for `v1`, otherwise it would later not be able to use the value of `v1` in a constant expression, but it must be able to :) – Johannes Schaub - litb Jan 23 '13 at 17:28
  • 1
    @JohannesSchaub-litb: Initially I wanted to write `constexpr` instead of `const` in the first four initialization, but then I saw that I could not use it for the next four. So I avoided it altogether, thinking that if I use `constexpr` in first four, and `const` in next four, then that will create confusion for the readers, and possibly might trigger a discussion, which we're doing now anyway. :P – Nawaz Jan 23 '13 at 18:33
6

Because for some domain of input parameters, the impure path will never be taken. For that domain, constexpr will work fine.

For instance, your function may have a simple branch and a more complicated branch. And you may specify that for your function to be usable in constant expressions, the function arguments must be such that this and that condition is met, yielding to the simple branch in your function that is always pure.

A useful sideeffect of this is that you can cause errors during constant computation. I.e if a precondition in the simple branch is violated, you could cause the evaluation of an impure expression, provoking a compile time error (an assertion or an exception is a good idea here, as it continues to complain when the function is invoked in a runtime context).

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212