constexpr

Pages: 12
Thanks Repeater that was very helpful ;)

applies to variables rather than functions

What about functions?
I see they already had people complain about it in the past:
https://github.com/isocpp/CppCoreGuidelines/issues/964

Hope you have better luck than they did.
Guys, with all respect, I see what I asked has been too complicated for me now and at times turned aside.

It was the first time I heard that name "constexpr" and I asked the question here to have something simpler not harder. I read all the messages but I couldn't understand the majority of them. Somewhere in the messages I felt that a const is const (immutable) but consexpr can be changed. So it's a variable and why do we still need it?!

I need something clear and simple with an easy example to illustrate what a "constexpr" is and why we need it. If you want to bring something rather advanced in your explanation for the question, please explain that too.

My routine is that I using VS write C++ console apps and press F5 to run them and see the output. And don't know what will carry out behind the hood.
Somewhere in the messages I felt that a const is const (immutable) but consexpr can be changed.

No. A const variable is const. A constexpr variable is const AND other things as well.
Last edited on
A constexpr is something that has a value (or with a function, a return) that is fixed at the time of compilation.

That's basically it.

It's a guarantee that the function will have a return value that can be safely calculated at compile time, instead of having to wait until the program is actually run.
I think constexpr is a relatively advanced topic and there is often no need to use it in most regular code.

Both const and constexpr can be used to define constants.

const just means that you are not allowed to modify the constant but it is not necessarily a compile-time constant because it might not be possible to calculate the value of the constant at compile-time.

Examle:
1
2
3
4
5
void foo(int a)
{
	const int b = 2 * a;
	...
}

In the above example b is a constant in the sense that it cannot be modified in the rest of the function, but it is not a compile-time constant because the value depends on what value that is being passed to the function so it is impossible to calculate the value at compile-time. It has to be calculated at runtime.

constexpr has the additional requirement that it has to be possible to calculate the value at compile-time. If this is not possible the compiler will give an error. This means you can only use other compile-time constants and constexpr functions when initializing the constexpr constant.

Examle:
1
2
3
constexpr int x = 3 * 10; // OK
constexpr int y = 3 * fun(10); // OK if fun is marked constexpr (and does not lead to an error internally)
constexpr int z = 3 * fun(e); // OK if fun is marked constexpr and e is also a compile-time constant 

Compile-time constants are necessary in some situations, e.g. when specifying the size of a local array or when passing template arguments.

Examle:
1
2
constexpr int size = 10;
int arr[size]; // creates an array of size 10 

This example would have worked even with const because const can sometimes also be used to create compile-time constants if the initialization expression fulfills the requirements for constexpr, but using constexpr makes the intention more clear, and would generate an error early if it fails to be a compile-time constant instead of later when you try to use it in a situation where a compile-time constant is required.
Last edited on
Thank you. I got your explanation to a good extent.

As for fun(e), please take a look at this code:

1
2
3
4
5
6
7
8
9
10
11
constexpr int foo(int a) { int b = a * 4; return b; }

int main()
{
	int e = 5;
	constexpr int cx = foo(e);
	cout << cx << endl;

	system("pause");
	return 0;
}


Here, we know that e is 5, so it's clear. Then e is sent to the function foo. There it will be multiplied by 4 and the rerun value which is also a constexpr is returned. So everything is clear for us and we know the result is 20. But why do I still get errors?

What is done in compile time exactly?

And about the second part of your answer:
when passing template arguments.


Would you please give a simple example of this case too.
Here, we know that e is 5, so it's clear. Then e is sent to the function foo. There it will be multiplied by 4 and the rerun value which is also a constexpr is returned. So everything is clear for us and we know the result is 20. But why do I still get errors?

We know, and the compiler might know, but the rules are a bit more strict. e has to be constexpr for foo(e) to evaluate to a constant expression.

They could of course have defined the rules such that if a non-const variable is initialized with a constant-expression, and it has not yet been modified (how do you define this?), then it can be used as a constant expression. This would probably complicate things unnecessarily and make the rules harder to explain. If you meant e to be used as a constexpr why not just define it as such?

Another option would have been to let the compiler use e as constexpr whenever it can but that would lead to even more problems. When we write code we often want to know that it will continue working but if it was up to the compiler to decide it could differ between different compilers or even depend on whether or not optimizations are turned on. This would just be terrible.


And about the second part of your answer:
"when passing template arguments."
Would you please give a simple example of this case too.

One example from the standard library is std::bitset.
http://www.cplusplus.com/reference/bitset/bitset/

It's a fixed size "container" for storing bit values. It is defined as a class template and the number of bits that you want to store is passed as a template argument.

Example:
1
2
// declares a bitset of size 3
std::bitset<3> bits;

Then you can use various operations to set bits, count bits, etc. It's not really relevant so I'm not going to explain how. The important part here is the size 3. It has be constant expression (e.g. a compile-time constant).

Example:
1
2
3
4
5
constexpr int number_of_bits = 3;
std::bitset<number_of_bits> bits; // OK

int non_const_size = number_of_bits;
std::bitset<non_const_size> bits2; // error, the size is not a constant expression 
Last edited on
At risk of repeating Peter87's words, it didn't work because a constexpr function needs constexpr input. This works fine:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;

constexpr int foo(int a) { int b = a * 4; return b; }

int main()
{
	constexpr int e = 5;
	constexpr int cx = foo(e);
	cout << cx << endl;

	return 0;
}


it didn't work because a constexpr function needs constexpr input

IMO, it's important to reiterate that this is only true in general if the call is part of a constant expression.

Again, we can call foo with arguments that are not constant expressions. If we do this, the result will only be known at run-time, barring the as-if rule - the compiler's license to optimize.

A function whose result is always a constant expression is a consteval (or immediate) function.... which isn't part of the language yet.

1
2
int e = 5;
int cx = foo(e); // constexpr function called at runtime: OK 
Last edited on
mbozzi wrote:

Core Guidelines PR here
https://github.com/isocpp/CppCoreGuidelines/pull/1307

merged, thanks!
^
Great, thanks everyone.
closed account (E0p9LyTq)
Whoa! consteval functions in C++20.

I am still trying to wrap my brain around C++11/14/17 stuff, and now this?

I feel like Engineer Scott, reading tech manuals is leisure time. :Þ
@Peter87:

Thanks for your explanation. I appreciate that. The problem still exits in my mind!

We can define e as a constexpr variable and the code will work well. But one thing: What's the difference between using const in lieu of constexpr in the code. Both run, but apparently there might be some difference. constexpr wouldn't be presented otherwise reasonably.

I think your answer goes to compile-time and run-time cases. I suppose, in compile-time, the compiler checks the code to merely determine if there's a syntax error or not. And at the run-time, the program is carried out and other types or errors will be caught (if any).

So, what do these two (compile-time and run-time) have to do with constexpr and const?
I won't understand the issue unless I figure out the things as such, step-by-step.
https://stackoverflow.com/questions/14116003/difference-between-constexpr-and-const

Just wondering if the OP has read this?In particular the first answer, it mentions where constexpr must be used.

Another thing, I had the impression (read somewhere) that* constexpr is stronger than const in that: constness can be cast away with const_cast, whereas constexpr is set in stone. Although use of const_cast is not recommended.

* But I am struggling to find that in the reference or the standard

http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-casts-const

https://en.cppreference.com/w/cpp/language/constant_expression
constexpr itself is not part of the type of an object, and so type conversions cannot affect it. However, constexpr implies const, and that implied const can be cast away.

The only correct reason to do that would be to pass a pointer or reference to it into an API that is not const-correct.
Actually modifying the object through the pointer would yield undefined behavior.

... example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <type_traits>

// bad practice: const-incorrect function
// the referent is not modified, so this is well-formed code.
void f(int &x) {}

int main()
{
    constexpr int a = 0;
    static_assert(std::is_same_v<decltype(a), const int>, 
                  "the type of a is const int");

    f(const_cast<int&>(a));
}
Last edited on
Topic archived. No new replies allowed.
Pages: 12