Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constant integers and constant evaluation

Consider the following program:

#include <iostream> #include <type_traits>  constexpr int f() {   if (std::is_constant_evaluated())     return -1;   else return 1; }  int main() {   int const i = f();   std::cout << i; } 

It prints -1 when run (wandbox).

However, if I make the function throw when evaluated at compile time::

#include <iostream> #include <type_traits>  constexpr int f() {   if (std::is_constant_evaluated())     throw -1; // <----------------------- Changed line   else return 1; }  int main() {   int const i = f();   std::cout << i; } 

it compiles fine and outputs 1 (wandbox). Why didn't I get a compile failure instead?

like image 818
Pilar Latiesa Avatar asked Sep 06 '19 15:09

Pilar Latiesa


People also ask

What is constant evaluation?

Constant evaluation is the process of computing the result of expressions during compilation. Only a subset of all expressions can be evaluated at compile-time.

What is an integer constant expression?

An integer constant is a value that is determined at compile time and cannot be changed at run time. An integer constant expression is an expression that is composed of constants and evaluated to a constant at compile time.

What is constant expression in C++?

A constant value is one that doesn't change. C++ provides two keywords to enable you to express the intent that an object is not intended to be modified, and to enforce that intent. C++ requires constant expressions — expressions that evaluate to a constant — for declarations of: Array bounds.

What is meant by constant expression?

A constant expression is an expression that can be evaluated at compile time. Constants of integral or enumerated type are required in several different situations, such as array bounds, enumerator values, and case labels. Null pointer constants are a special case of integral constants.


1 Answers

Isn't constant evaluation fun?

There are a few places in the language where we try to do constant evaluation and if that fails, we fallback to doing non-constant evaluation. Static initialization is one such place, initializing constant integers is another.

What happens with:

int const i = f(); 

is that this could be constant evaluation, but it doesn't necessarily have to be. Because (non-constexpr) constant integers can still be used as constant expressions, if they meet all the other conditions, we have to try. For instance:

const int n = 42;       // const, not constexpr std::array<int, n> arr; // n is a constant expression, this is ok 

So try we do - we call f() as a constant expression. In this context, std::is_constant_evaluated() is true, so we hit the branch with the throw and end up failing. Can't throw during constant evaluation, so our constant evaluation fails.

But then we fallback, and we try again - this time calling f() as a non-constant expression (i.e. std::is_constant_evaluated() is false). This path succeeds, giving us 1, so i is initialized with the value 1. But notably, i is not a constant expression at this point. A subsequent static_assert(i == 1) would be ill-formed because i's initializer was not a constant expression! Even though the non-constant initialization path happens to (otherwise) entirely satisfy the requirements of a constant expression.


Note that if we tried:

constexpr int i = f(); 

This would have failed because we cannot fallback to the non-constant initialization.

like image 130
Barry Avatar answered Oct 02 '22 08:10

Barry