3

Consider code like the following, where the literal some_magic_int (e.g. 3) is given a name just to make a bit clearer what constant it represents:

void f() {
    static constexpr int this_variable{some_magic_int};
    do_something_with(this_variable);
}
int main() {
    // ...
    f();
    // ...
}

I'm pretty sure constexpr has to be here: some_magic_int is literal, so it never changes, and I'm giving it a name just for clarity, not to give a mean to change it, so it should be at least const; then why not constexpr to have it at compile-time?

But what about static? Is it just unnecessary? Or is it detrimental? If so, why? And also, does it have any observable effect, when paired with constexpr in the declaration of a local variable?


As regards the question to which this is marked as duplicate of, it is about static constexpr int x [] = {}, and not static constexpr int x {}. This highlights at least one difference between that case (attributes applying to x pointer vs attributes applied to *x pointee) and my case (there's no pointer).


Furthermore, once I add constexpr to the specifier of a local variable (where it makes sense, e.g. to a int), I'm saying that variable is compile-time known. Why in the world doesn't that imply that no run-time entity is needed whatsoever?

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • Does this answer your question? https://stackoverflow.com/questions/13865842/does-static-constexpr-variable-inside-a-function-make-sense. TLDR: The short answer is that not only is static useful, it is pretty well always going to be desired – Pepijn Kramer Sep 10 '21 at 08:14
  • 1
    You can also see the difference in assembly - [godbolt](https://godbolt.org/z/xn1MWWzM1). non-static is really stored on stack, `static` is program-wide. – Quimby Sep 10 '21 at 08:17
  • @PKramer I don't get why a plain `int` must be stored statically. Isn't the main point of this `constexpr` to _name_ a magic number (which is, of course, good style)? Nevertheless, I wouldn't have any problem if it would be a local with good chances to be inlined in the generated code instead of consuming storage anywhere. If it would be an array of `int`s or a string literal that would be another story... – Scheff's Cat Sep 10 '21 at 08:28
  • 2
    I believe this question is different from the duplicate topic since the [answer](https://stackoverflow.com/a/13867690/4123703) doesn't elaborate on `static constexpr` integer, but focuses on `static constexpr` integer array. Which should be different topics. – Louis Go Sep 10 '21 at 08:45
  • @Scheff'sCat Quimby's example example shows that by taking address of constexpr. To take an address of constexpr variable, the value of variable should be stored somewhere, if it's not static, value of address would be automatic (so each time it might be a new address). `static constexpr` would result in constant value of pointer. – Swift - Friday Pie Sep 10 '21 at 08:47
  • no the main goal of "constexpr" is to tell the compiler to try evaluate an expression at compile time. static (local variable) means make it a value that's only initialized once (runtime) . So you have the good idea, replace a magic number with something with a name (without using macros). You don't need to store the int (statically or not) if you're not going to change the value at runtime. – Pepijn Kramer Sep 10 '21 at 08:47
  • I found [this](https://stackoverflow.com/a/41125798/4123703) might be relevant to this question. In C++17, `constexpr static` data member variables will be inline too. That means you can omit the out of line definition of `static constexpr` variables, but not `static const`. – Louis Go Sep 10 '21 at 08:50
  • `constexpr` in runtime is the same as `const`, so `constexpr int` will not be inlined, but `static constexpr int` would be inlined after C++17. [Demo](https://godbolt.org/z/nTqvdoGse) – Louis Go Sep 10 '21 at 08:53
  • 3
    @LouisGo that's incorrect. THere is no guarantee of that although it's possible optimization. local `static` defines initialization time, without it `this_variable` is constexpr value while `&this_variable` is not. If it is static, `&this_variable` is a constexpr value because that object got process-wide life span. – Swift - Friday Pie Sep 10 '21 at 08:54
  • 1
    @Swift-FridayPie I already noticed that it's a difference in general whether a variable is used by value in opposition to taking it's address. In OPs question, I don't see any attempt to make use of its address. – Scheff's Cat Sep 10 '21 at 09:01
  • @Swift-FridayPie, maybe you can vote to reopen the question too and give your answer? – Enlico Sep 10 '21 at 09:02
  • @Scheff'sCat, no, I haven't intended to show a use of its address, but I guess in my example `do_something_with` might take the input by `const&` and then use its address somehow. I'm just throwing a random thought, though. – Enlico Sep 10 '21 at 09:04
  • Hmm... Why should an _`int`_ taken by `const int&`? Even if it would, the compiler will care about this. IMHO, there are more chances that the function would take a plain `int`, and your `constexpr int` would end up in no additional code (which I would consider as best case: a named magic number with no additional cost at run-time). ;-) – Scheff's Cat Sep 10 '21 at 09:06
  • I don't know, @Scheff'sCat, `do_something_with` could be a template function taking `T const&`. – Enlico Sep 10 '21 at 09:20
  • @Scheff'sCat a number of API's existing take input values by reference because of POSIX's like design... in general, "do something" is do anything and that may include taking address or reference or return by reference (why we would need a static otherwise? static constexpr IS an object with process life length, we can't get address or reference of automatic constexpr and use it as const). Most cases I encountered were actually return by reference cases. – Swift - Friday Pie Sep 24 '21 at 10:12

1 Answers1

1

The standard doesn’t actually ever talk about compile-time anything except to say that types are checked and templates are instantiated before execution. That means that this program must be diagnosed (not “rejected”!) even though the non-constant array length and template argument are never “used” and might plausibly be ignored by an interpreter:

template<int> void f() {}
int main(int argc,char **argv) {
  if(false) {
    int buf[argc];  // accepted by a common extension
    f<argc>();
  }
}

Beyond that, the semantics are that every evaluation is part of the ordinary execution of the program and constant folding is just an as-if optimization like any other. (After all, we can optimize argc*2*3*4 even though it contains no non-literal subexpression that could be a constant expression.) For constant expressions, this is largely unobservable because constant evaluation can’t have side effects (which also avoids interactions among constant-initialized non-block variables). It can, however, make a difference via the address of the local variable:

bool introspect(const int *p=nullptr) {
  constexpr int x=0;
  return p ? p==&x : introspect(&x);  // always false
}

That the compiler must make such variables occupy memory if their address escapes is much of the content of the answers to the previously marked duplicate. It therefore makes sense to prefer static except perhaps when the object is large and its address doesn’t matter (e.g., for use as a template argument or via recursion).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76