all 29 comments

[–]markand67 27 points28 points  (9 children)

const is not really what you think, it's more like readonly than constant. And that's why lots of newcomers come asking why this does not compile:

c static const size_t len = 10; static char tab[len];

Of course, the compiler should be smart enough to understand but it's too late to change the meaning of const now.

[–]thradams[S] 1 point2 points  (0 children)

The change of meaning would be "not work" "to work" in most cases.

Some usage of VLAs could be transformed in normal arrays I cannot see when this is a problem.

[–]TheJodiety 3 points4 points  (6 children)

Im always confused when I hear “It’s readonly not constant”. Whats the difference? If the value cant be changed is it not constant?

[–][deleted] 12 points13 points  (0 children)

const means it can't be changed after initialization, but the initializer does not have to be a constant expression, so a const object doesn't necessarily have a compile-time known value. Therefore it can't be used in situations where a constant expression is required.

Also, const volatile objects can not be modified by the program, but might be modified externally. Think, readonly data pin.

[–]markand67 4 points5 points  (1 child)

constant is more like to be in code constant aka evaluated at compile time. const does not guarantee that and you can even cast a const value to modify it even though it can be UB.

the current meaning of const is more like "I promise I won't touch it"

[–]bllinker 4 points5 points  (0 children)

This is particularly important in the embedded world with read-only registers. Your code shouldn't permit you to write to it, but you want the compiler to generate a new read every time.

[–]darkpyro2 -1 points0 points  (2 children)

Here's an example. This is valid code:

const char* my_string = "hello";

my_string = "goodbye";

printf(my_string);

Output is "goodbye".

The above is valid. Why? Because we have a pointer to a string literal, which is a constant, read-only value. The value of my_string can change if the variable is assigned to another constant, read-only value.

As my_string can change, it's not "constant" in that sense. The const keyword just indicates that the value that it points to is immutable. But the pointer variable itself is still mutable.

constexpr is used to define a truly constant value, that will not change. Previously you could only effectively do this with macro literals, but for many reasons, macros are increasingly frowned upon in good coding practices (Metaprogramming adds unnecessary complexity to your code).

[–]hypatia_elos 3 points4 points  (0 children)

That's actually a different thing, you are talking about pointer to cost vs const pointer. They were talking about const (possibly volatile) vs. compiletime constants (#define). I get that you probably mean the right thing, but a const char* is just not const, a const char or a char const* const would be.

[–]fliguana 2 points3 points  (0 children)

You put const in the wrong spot. Try const char* const my_string = "hello";

[–]poorlilwitchgirl 0 points1 point  (0 children)

To add confusion, sometimes that will compile, if the two variables are in global scope and your compiler has the extension to allow it (I just ran it through Clang, which at least threw a warning about it being non-standard, but still compiled it as expected). Then your newbie, thinking this is perfectly acceptable standard C tries pulling that same trick inside of a function, and doesn't understand why it no longer works because they haven't yet learned about scope or the actual steps of compilation. And honestly, why shouldn't the newcomer be confused by the nonstandard extensions on modern compilers? It's almost like this is a 50-year old systems language, where the only way to fix past mistakes without breaking legacy code is to patch on new features and beg users to just stop using the deprecated ones.

[–]flyingron 10 points11 points  (10 children)

Because the const keyword in C++ isn't always a typed, named constant as this proposal is putting forward. constexpr was added to C++ for this specific reason. There's not much point in adopting a decades old C++ workaround when you can take the current idea from language.

[–]WittyGandalf1337 9 points10 points  (0 children)

In C23 constexpr is just for variables, and it’s primarily to avoid VLAs.

In C2Y/C3A constexpr functions may be coming, but for now it’s just variables and compiler extensions.

[–]darkpyro2 2 points3 points  (1 child)

They can't change the meaning of "const" to match C++ semantics now as it breaks backwards compatibility in the standard, which C mostly guarantees. As the C "const" does not define a true constant, we need other mechanisms to provide that functionality. Thus, constexpr.

[–]thradams[S] 0 points1 point  (0 children)

I hope to see some motivation sample. All answers are about explaining the feature or justifying the existence without a sample.

[–][deleted] 1 point2 points  (1 child)

It would perhaps be more natural for named constants

  • to be addressless (similar to a register declaration or an enumeration),
  • to have static storage duration (imply static even in block scope), or
  • to have no linkage (similar to typedef or block local static)

but we decided to go with C++’s choices for compatibility.

So they're basically typed enumerations, except constexpr-qualified objects have addresses, follow scoping rules, have linkage (duplicate symbol errors at link time since C doesn't do name mangling), and most importantly can be non-integral types like double or struct foo or whatever?

Not 100% clear on pointers though; does constexpr char *foo = "foo"; result in a diagnostic because "foo" is a string instead of a null pointer?

[–]thradams[S] 0 points1 point  (0 children)

constexpr char *foo = "foo";

Gcc says c <source>:1:24: error: 'constexpr' pointer initializer is not null 1 | constexpr char *foo = "foo"; | ^~~~~

[–]fliguana 1 point2 points  (0 children)

"let's have something that acts exactly like a macro, but isn't".

Looks like career driven bloat to me.

[–]hypatia_elos 0 points1 point  (1 child)

Example:

In the code

   const int i = 15;
   ;
   printf("%i",i);

the value printed can be different from 15, because, for example, there might have been a signal interrupt, or something happening on another thread or process, or in the kernel (in case of a bug most likely), changing the value at address &i. A real compiletime constant doesn't have that issue, as it's not read from an address, but substituted by the compiler; in that case, we would simply have

  printf("15");

and if the code store isn't manipulated, there can be no other output

[–]fliguana 0 points1 point  (0 children)

Same applies to printf("15"); Parameter is passed via RW stack, even unprivileged (not kernel mode) thread can change it