I'm confused to what it means to be known at compile time. From the code below, can the compiler not calculate the value of n even if I have passed a constant literal value 90 as an argument? Why does it give me an error that expression must have a constant value
constexpr int MAX_expr = 100;
const int MAX = 90;
void foo(int n)
{
constexpr int cExpr1 = MAX_expr + 7;
constexpr int cExpr2 = n + 7;
constexpr int cExpr1 = MAX + 7;
constexpr int cExpr2 = n + 7;
const int cExpr1 = MAX_expr + 7;
const int cExpr2 = n + 7;
const int cExpr1 = MAX + 7;
const int cExpr2 = n + 7;
}
int main() {
foo(90);
const int i = factorials(90);
}
With that same logic, shouldn't factorials(int i) give an error because it does not know what argument is going to be passed therefore, the compiler won't be able to compute what is going to be returned?
constexpr int factorials(int i) {
return i > 1 ? i * factorials(i - 1) : 1;
}
The constexpr keyword can be confusing. It can be applied to both variables and functions, but with totally different meanings, except that they both have something to do with constant expressions.
A variable declared with constexpr must be initialized by a constant expression. In your code, n + 7 is not a constant expression because the value of n is not known until the function is called and may vary from one call to the next. What if the user entered some integer, and then you passed that integer to foo? Obviously, that number plus 7 is not something you can call "known at compile time". Because of that, a function definition such as foo is not allowed. You cannot promise the compiler that you will only ever pass a constant expression argument. If you can, then promote n to a template parameter, and the code will work.
In contrast, constexpr applied to a function doesn't guarantee that calling the function produces a constant expression. It allows the function to be called in a constant expression, and places constraints on the definition in order to make this possible. While factorial will certainly not produce a constant expression if given a runtime argument, it will produce a constant expression if given an integer constant expression as an argument (assuming no overflow). Thus, unlike initializers of constexpr variables, a constexpr function is allowed to contain constructs that may or may not have compile-time constant values.
Related
I know, that such questions were asked early (for example non-constexpr calls in constexpr functions), but let's take next code:
consteval int factorial(int n)
{
return n <= 1 ? 1 : (n * factorial(n - 1));
}
factorial(5);
All is OK. We guarantee, that factorial(5) expression is resolved at compile time, because consteval. Right? If so, I think it should mean, that recursive factorial(n - 1) in call factorial(5) is resolved at compile time too. However, we too know, that in declaration int factorial(int n) parameter int n is just a variable, not constexpr. And this influences, if we try to do something like this:
consteval int factorial(int n)
{
// 1
constexpr auto res = factorial(n - 1); // error: ānā is not a constant expression
// 2
return n <= 1 ? 1 : (n * factorial(n - 1)); // hhhhmmmmmm...but all is ok..
}
factorial(5);
What we have?
We call consteval function with literal constant. OK.
Within consteval function we make recursive call to this function with non constexpr local parameter at row 2. And all is OK, though we call consteval function with non-constexpr value. Well, we can suggest, that compiler knows, that base call has been done as right consteval call factorial(5), and the whole final expression (with all internal code of factorial) should be interpreted as consteval. Yes? Or, why? Because...
At row 1 we explicitly make a call as constexpr with non-constexpr value. And we get an error.
My question is next: why for explicit consteval call of factorial(5) compiler makes difference between explicit and implicit constexpr recursion call of factorial? Is it bug or feature?
Let's review what a constant expression is. A core constant expression is an expression which, when evaluated, does not cause one of a long list of "bad" behaviors. A constant expression is a core constant expression whose result is "allowed" by some other rules (not important here). In particular, note that these conditions are heavily non-syntactic: a constant expression are not defined positively by defining what expressions are constant expressions, but negatively by defining what constant expressions can't do.
A result of this definition is that an expression can be a constant expression even it requires the evaluations of many non-constant expressions (even non-core constant expressions). In the definitions
consteval int factorial1(int n) {
if(n == 0) return 1;
else { // making this correct since undefined behavior interferes with constant expressions
/*constexpr*/ auto rec = factorial1(n - 1);
return n * rec;
}
}
consteval int factorial2(int n) {
return n == 0 ? 1 : n * factorial2(n - 1);
}
the factorial1(n - 1) in factorial1 is not a constant expression, so adding constexpr to rec is an error. Similarly, the n == 0 ? 1 : n * factorial2(n - 1) in factorial2 is also not a constant expression. The reason is the same: both of these expressions read the value of (perform lvalue-to-rvalue conversion on) the object n, which did not start lifetime within the expression. But this is fine: the bodies of constexpr/consteval functions are simply not checked for being constant expressions. All constexpr really does is whitelist a function's calls for appearing in constant expressions. And, again, an expression can be constant (like factorial1(5)) even if you need to evaluate a non-constant expression on the way (like factorial(n - 1)). (In this case, when evaluating factorial1(5), the lifetime of the n object that is the parameter to factorial does start its lifetime within the expression being checked, so it can be read during evaluation.)
Two places where an expression will be checked for being a constant expression are initializations of constexpr variables and "non-protected" calls to consteval functions. The first one explains why adding constexpr to rec in factorial1 is an error: you're adding an additional check for a constant expression that is not done in the correct factorial1 function, and this extra check (correctly) fails. This should have answered your point 3.
For your point 2: yes, there's a special "protection" for consteval functions called from other consteval functions. Usually, a call to a consteval function is, right at the point it is written, checked for being a constant expression. As we've been discussing, this check would fail for the calls factorial1(n - 1) and factorial2(n - 1) in the above definitions. There is a special case built into the language to save them: a call to a consteval function in an immediate function context (basically, whose immediately enclosing function is also consteval) is not required to be a constant expression.
There is a very neat property of constant expressions in C++: their evaluation cannot have undefined behavior (7.7.4.7):
An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:
...
an operation that would have undefined behavior as specified in [intro] through [cpp] of this document [ Note: including, for example, signed integer overflow ([expr.prop]), certain pointer arithmetic ([expr.add]), division by zero, or certain shift operations ā end note ] ;
Trying to store the value of 13! in a constexpr int indeed yields a nice compile error:
constexpr int f(int n)
{
int r = n--;
for (; n > 1; --n) r *= n;
return r;
}
int main()
{
constexpr int x = f(13);
return x;
}
Output:
9:19: error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = f(13);
^ ~~~~~
4:26: note: value 3113510400 is outside the range of representable values of type 'int'
for (; n > 1; --n) r *= n;
^
9:23: note: in call to 'f(3)'
constexpr int x = f(13);
^
1 error generated.
(BTW why does the error say "call to 'f(3)'", while it is a call to f(13)?..)
Then, I remove constexpr from x, but make f a consteval. According to the docs:
consteval - specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant
I do expect that such a program would again cause a compile error. But instead, the program compiles and runs with UB.
Why is that?
UPD: Commenters suggested that this is a compiler bug. I reported it: https://bugs.llvm.org/show_bug.cgi?id=43714
This is a compiler bug. Or, to be more precise, this is an "underimplemented" feature (see the comment in bugzilla):
Yup - seems consteval isn't implemented yet, according to: https://clang.llvm.org/cxx_status.html
(the keyword's probably been added but not the actual implementation support)
I understand that the size of the built-in array must be a constant expression:
// Code 1
constexpr int n = 5;
double arr[n];
I do not understand why the following compiles:
// Code 2
const int n = 5;
double arr[n]; // n is not a constant expression type!
Furthermore, if the compiler is smart enough to see that n is initialized with 5, then why does the following not compile:
// Code 3
int n = 5;
double arr[n]; // n is initialized with 5, so how is this different from Code 2?
P.S. This post answers using quotes from the standard, which I do not understand. I will very much appreciate an answer that uses a simpler language.
n is not a constant expression type!
There is no such thing as a constant expression type. n in that example is a expression, and it is in fact a constant expression. And that is why it can be used as the array size.
It is not necessary for a variable to be declared constexpr in order for its name to be a constant expression. What constexpr does for a variable, is the enforcement of compile time constness. Examples:
int a = 42;
Even though 42 is a consant expression, a is not; Its value may change at runtime.
const int b = 42;
b is a constant expression. Its value is known at compile time
const int c = rand();
rand() is not a constant expression, and so c is neither. Its value is determined at runtime, but may not change after initialisation.
constexpr int d = 42;
d is a constant expression, just like b.
constexpr int f = rand();
Does not compile, because constexpr variables must be initialised with a constant expression.
then why does the following not compile:
Because the rules of the language don't allow it. The value of n is not compile time constant. The value of a non-const variable can change at runtime.
The language cannot have a rule that some value doesn't change at runtime, then it is a constant expression. That would not be of any use to the programmer since they cannot assume which compiler will be able to prove the constness of which variable.
The language has to exactly specify the cases where an expression is constant. It would also be infeasible to specify that a non-const variable is a constant expression if it hasn't been modified before its use, because it is impossible to prove in most cases, even though you've found one case where the proof happens to be easy.
// n is not a constant expression type!
But it is. Per [expr.const]/3
A variable is usable in constant expressions after its initializing declaration is encountered if it is a constexpr variable, or it is a constant-initialized variable of reference type or of const-qualified integral or enumeration type. An object or reference is usable in constant expressions if it is [...]
a complete temporary object of non-volatile const-qualified integral or enumeration type that is initialized with a constant expression.
So, if you have a const integer intialized with a constant expression then you still have a constant expression as nothing can change. This is a rule that existed before constexpr was ever a thing as it allowed programmers to initialize arrays with constant variables instead of using macros.
Furthermore, if the compiler is smart enough to see that n is initialized with 5, then why does the following not compile:
Because the integer is not const so it could be changed. Even though in your case you can prove it can't change, in general you can't so it is just not allowed.
A value declared constexpr means that this value does not change and is known during compile time.
A value declared const means that this value does not change after initialization, but it is not mandatory to be known during compile time. In other words, a constexpr is const, but a const is not constexpr.
Your "Code 3" example doesn't work because you need a constant known at compile time in order to allocate memory for a vector, so you need a constexpr.
What is the difference between unsigned int a=2 and int a=2U
Also,
sizeof(a) operator gives the same value for int a=2 and int a=2L , why? Shouldn't the size be doubled.
UPDATE:
Thanks all for the answers.
here is the summery:
long int or int are types with which variable is declared. 2 or 2L is the value with which variable is initialised.
Size of the variable is declared by type instead of its initialisation, so both will have same size
In C++, all variables are declared with type. C++ forces1 you to specify the type explicitly, but doesn't force you to initialize the variable at all.
long int a = 2;
long int b = 2L;
long int c;
This code makes 3 variables of the same type long int.
int a = 2;
int b = 2L;
int c;
This code makes 3 variables of the same type int.
The idea of type is roughly "the set of all values the variable can take". It doesn't (and cannot) depend on the initial value of the variable - whether it's 2 or 2L or anything else.
So, if you have two variables of different type but same value
int a = 2L;
long int b = 2;
The difference between them is what they can do further in the code. For example:
a += 2147483647; // most likely, overflow
b += 2147483647; // probably calculates correctly
The type of the variable won't change from the point it's defined onwards.
Another example:
int x = 2.5;
Here the type of x is int, and it's initialized to 2. Even though the initializer has a different type, C++ regards the declaration type of x "more important".
1 BTW C++ has support for "type inference"; you can use it if you want the type of the initializer to be important:
auto a = 2L; // "a" has type "long int"
auto b = 2; // "b" has type "int"
What is difference between "long int a=2" and "int a=2L"?
The former defines a variable a as having type long int initialised from the value 2, the latter defines it as having type int initialised from the value 2L. The initialiser is implicitly converted to the type of the variable, and does not affect the type of the variable.
Or what is the difference between long char c='a' and char c=L'a'
The former defines a variable c as having type long char initialised from the value 'a', the latter defines it as having type char initialised from the value L'a'. Since the type long char doesn't exist, the former is an error. The type of L'a' is called wchar_t, not long char, and in the latter case is again converted to the type of the variable.
or what is the difference between unsigned int a=2 and int a=2U
The former defines a variable a as having type unsigned int initialised from the value 2, the latter defines it as having type int initialised from the value 2U. Yet again, the initialiser does not affect the type of the variable.
Also,
sizeof(a) operator gives the same value for int a=2 and int a=2L , why? Shouldn't the size be doubled.
Since they both define a as type int, sizeof(a) should give sizeof(int) for both.
#include <stdio.h>
int main()
{
int a1=2; //a1=2
int a2=2L; //a2=2
int a3=2.5673; //a3=2
int a4='A'; //a4=65
return 0;
}
Here, even though the value of a3 and a4 is float and char respectively, the value will be transformed to int as a3 and a4 is declared as an int. In the same way, the value of a2 will be transformed into int even though the value was set as 2L.
The variable doesn't depend on the value, rather on type declaration. int a will always be an integer, no matter what's it's value is.
Consider the following code:
constexpr unsigned f(unsigned x)
{
while (x & 1) x *= 3;
return x;
}
int main()
{
char a[f(2)];
char b[f(1)];
}
In case it isn't obvious: for odd integers x, the function f never terminates.
When I compile the above program with clang on coliru, b seems to be a VLA, but not a:
warning: variable length arrays are a C99 feature [-Wvla-extension]
char b[f(1)];
Is there a well-defined limit at which the compiler decides to stop evaluation of a constant expression? Or would it be perfectly fine for a conforming compiler to go into an infinite loop? Does f(1) yield UB?
There are a number of things which means that an expression is
not a core constant expression is
-- an invocation of a constexpr function or a constexpr constructor that would exceed the implementation defined recursion limits;
(fifth point in Ā§5.19/2.). So the limit is implementation
defined.