Use "constexpr" rather than "static const"?

I noticed in @mmuetzel 's last check-in (octave: 014030798d5e) that there was use of static const to declare some constants. As far as I understand, static const will create an actual variable which occupies memory on the program stack during execution. Would it be better to use C++ constexpr so that constants are formed at compile-time and put into the program (read-only) memory space?

I made just one change to test and Octave compiled just fine.

diff -r 79edd49a5a97 liboctave/util/oct-string.cc
--- a/liboctave/util/oct-string.cc	Mon Mar 28 19:52:24 2022 +0200
+++ b/liboctave/util/oct-string.cc	Mon Mar 28 14:04:49 2022 -0700
@@ -700,7 +700,7 @@ rational_approx (T val, int len)
   if (len <= 0)
     len = 10;
 
-  static const T out_of_range_top
+  constexpr T out_of_range_top
     = static_cast<T>(std::numeric_limits<int>::max ()) + 1.;
   static const T out_of_range_bottom
     = static_cast<T>(std::numeric_limits<int>::min ()) - 1.;
2 Likes

It’s fine with me if we use constexpr instead of static const in all the places where it is possible to make that change.

Do we also have any functions that just return constant values that can be computed at compile time? If so, we can also tag those with constexpr, correct?

Good idea. I didn’t check that before, but numeric_limits<T>::max() is constexpr since C++11:
https://en.cppreference.com/w/cpp/types/numeric_limits/max

We could probably also tag the max and min member functions of the Octave integer types as constexpr and allow them to be evaluated on compilation time. I haven’t checked that yet though.

I started to look into this. But I’m unsure about the best way to change this:
If we use constexpr in those expressions, is it still useful keeping them static?
IIUC, that would force the compiler to actually initialize those variables once. Without static, it might “optimize” that variable out entirely (and just “inline” its value where it is used). On the other hand, that might mean that these variables might be initialized multiple times (at each function call) on runtime in case the compiler didn’t “inline” them.

I guess that we’d need to decide on a case-by-case basis whether it makes sense to replace static const by either static constexpr, constexpr, or leave it as it is. We could probably drop static for all “scalar” values of POD type and hope that a “good” compiler will “inline” these values…
Am I missing something?

I’m attaching a wip patch that compiles for me with GCC 11.2.0 without any new warnings or errors. The test suite still passes.
constexpr-wip.patch (15.7 KB)

1 Like

I don’t think so. As you suggest, if we use the keyword static we may end up with a persistent variable depending on how clever the compiler is. If static is omitted I think most compilers will simply inline the value that the expression evaluates to.

[EDIT]
I’v done some more reading on Stack Overflow and on cppreference.com and I’m now mostly confused. I think a lot of the optimization depends on the actual compiler, and not on the C++ standard. Using constexpr is at least equivalent to using const. And it definitely says that the value can be computed at compile time rather than run time. This should be an extra hint to the compiler to understand that the value is not going to change (hence don’t need to actually have memory storage for a variable quantity available at runtime), and that the value is known to the compiler at compile time rather than runtime so it doesn’t need to perform runtime initialization (hence, it can simply swap the read-only value into the code).

I started out with a comment about this probably coming down to the behavior of the actual compiler you are using rather than the standard. I think that is still true. If we think the average compiler is stupid than the static keyword is important because at least variable initialization will happen just once at the start of the program. The downside is that we may be instructing the compiler to create memory storage and use static-initialization procedures which it would otherwise optimize out and just inline the constant value.

For testing purposes, I compiled the following program with g++ -g -O2

#include <iostream>
#include <cstdlib>

using namespace std;

static double const3 = 3.0;

int main()
{
  static constexpr double const1 = 1.0;
  constexpr double const2 = 2.0;

  double var1;

  cout << "Enter var1: ";
  cin >> var1; 

  cout << "var1 + const1: " << var1 + const1 << endl;
  cout << "var1 + const2: " << var1 + const2 << endl;
  cout << "var1 + const3: " << var1 + const3 << endl;

  return EXIT_SUCCESS;
}

I tried to use readelf to inspect the various sections of the resulting binary, but I’m not that familiar with the tool. Instead, I started the executable with gdb and then queried the address of the constants and variables.

(gdb) info address const1
Symbol "const1" is constant.
(gdb) info address var1
Symbol "var1" is a complex DWARF expression:
     0: DW_OP_fbreg -32
.
(gdb) info address const2
Symbol "const2" is constant.
(gdb) info address const3
Symbol "const3" is optimized out.

At least for g++, it seems to do the right thing regardless of which syntax is used.

1 Like

Final thought, I believe then that we should skip the static keyword. If the constant is being defined in a file outside of a function it will implicitly pick up that keyword. Within a function, it seems that a reasonable compiler will do the right thing regardless of the static keyword and therefore why should we bother with the extra, unnecessary qualifier.

1 Like

@rik: I agree with your analysis for scalar values. But what about (large) vectors that are indexed dynamically, e.g. in quadcc.cc? Wouldn’t it be better to keep those static?

I just tried the same debugger test I used before with quadcc.cc. Results:

(gdb) info address xi
Symbol "octave::xi" is constant.
(gdb) info address skip
Symbol "skip" is static storage at address 0x7fc5b5800f90.

The static const variable declared at the file level is a constant, but a static const variable within the function Fquadcc is an actual static variable.

If I change the code to drop the static keyword

  //static const int skip[4] = { 8, 4, 2, 1 };
  const int skip[4] = { 8, 4, 2, 1 };

and try again then skip is an ordinary variable (i.e., taking room on the stack and having to be initialized every single time the function is called).

(gdb) info address skip
Symbol "skip" is a complex DWARF expression:
     0: DW_OP_fbreg -288

Interestingly, there is a scalar variable at the same point in the code in quadcc.cc

  static const int ndiv_max = 20;

and this is properly made a constant by g++.

So, my rules derived from experiments with g++ are

  1. Use static const for constant scalars or arrays
  2. Use static constexpr only when the initialization actually is an expression that requires evaluation at compile time. For example static constexpr int a = 1 + 2;

For constants declared at file scope, this will result in constants always (regardless of scalars or vectors).
For constants declared within a function’s scope, this will result in constants for scalars and static variables for arrays which are initialized just once.

1 Like

Instead of “requires evaluation at compile time” I would say use static constexpr when it is possible to compute the initializer value at compile time. The same applies to functions that can be computed at compile time.

I’m a little confused. I thought we figured earlier that it might be of advantage to use constexpr for constant scalars (if the initializer value can be obtained at compile time). (Because the compiler might inline that value.)
@rik: Why should we prefer static const?

From what I gathered from @jwe’s reply, we’d want to use static constexpr for constant arrays where that is possible, and only resort to static const if the initializer value cannot be obtained at compile time.
Is that correct?

Edit: Would this decision tree be correct for constants in C++ code in Octave?
If the initializer value cannot be obtained at compile time, use static const.
Else, if the initializer value is a scalar (or if there is no dynamic access to array elements), use constexpr.
Else, use static constexpr.

Follow up to several questions.

My wording was less precise than jwe’s, but the intent is the same. If the Right-Hand Side (RHS) expression (the initializer) can be computed at compile time then using constexpr is a useful hint to the compiler. However, if the RHS does not require any computation because it is a constant already then I don’t see the need to treat it as an expression. In this case const and constexpr are effectively the same. My personal preference would be to write

const int meaning_of_life = 42;

rather than

constexpr int meaning_of_life = 42;

But,

constexpr int meaning_of_life = (40 + 1 + 1);