﻿# Design and evolution of constexpr in C\+\+

constexpr is one of the magic keywords in modern C\+\+\. You can use it to create code, that is then executed before the compilation process ends\. This is the absolute upper limit for software performance\.


> We published and translated this article with the copyright holder's permission\\\. The author is Evgeny Shulgin, email \\\- \[izaronplatz@gmail\\\.com\]\(mailto:izaronplatz@gmail\.com\)\\\. The article was \[originally\]\(https://habr\.com/en/post/579490/\) published on Habr\\\. We'd also like to invite you to read other theoretical articles that have a hashtag \[\\\#Knowledge\]\(https://pvs\-studio\.com/en/blog/posts/?tag\=Knowledge\)\\\.

_constexpr_ gets new features every year\. At this time, you can involve almost the entire standard library in compile\-time evaluations\. Take a look at [this code](https://godbolt.org/z/MYTbbsqvT): it calculates the number under 1000 that has the largest number of divisors\.

_constexpr_ has a long history that starts with the earliest versions of C\+\+\. Examining standard proposals and compilers' source code helps understand how, layer by layer, that part of the language was created\. Why it looks the way it does\. How _constexpr_ expressions are evaluated\. Which features we expect in the future\. And what could have been a part of _constexpr_ \- but was not approved to become part of the standard\.

This article is for those who do not know about _constexpr_ yet \- and for those who've been using it for a long time\.

![0909_constexpr/image1.png](https://import.viva64.com/docx/blog/0909_constexpr/image1.png)

## C\+\+98 and C\+\+03: Ranks among const variables

In C\+\+, sometimes it's necessary to use integer constants, whose values must be available at compile time\. The standard allows you to write constants in the form of simple expressions, as in the code below:

```cpp
enum EPlants
{
  APRICOT = 1 << 0,
  LIME = 1 << 1,
  PAPAYA = 1 << 2,
  TOMATO = 1 << 3,
  PEPPER = 1 << 4,
  FRUIT = APRICOT | LIME | PAPAYA,
  VEGETABLE = TOMATO | PEPPER,
};

template<int V> int foo();
int foo6 = foo<1+2+3>();
int foo110 = foo<(1 < 2) ? 10*11 : VEGETABLE>();

int v;
switch (v)
{
case 1 + 4 + 7:
case 1 << (5 | sizeof(int)):
case (12 & 15) + PEPPER:
  break;
}
```

These expressions are described in the **\[expr\.const\]** section and are called _constant_ _expressions_\. They can contain only the following:

* [Literals](https://eel.is/c++draft/lex.literal) \(this includes integers, these are integral types\);
* _enum_ values;
* An _enum_ or integral non\-type template parameter \(for example, the _V_ value from _template <int V\>_\);
* The _sizeof_ expression;
* _const_ variables initialized by a _constant expression_ – **this is the interesting point**\.

All the points except the last one are obvious – they are known and can be accessed at compile time\. The case with variables is more intriguing\.

For variables with static storage duration, in most cases, memory is filled with zeros and is changed at runtime\. However, it is _too late_ for the variables from the list above – their values need to be evaluated before compilation is finished\.

There are two types of _static initialization_ in the C\+\+98/03 standards:

1. _zero\-initialization_, when memory is filled with zeros and the value changes at runtime;
1. _initialization with a constant expression_, when an evaluated value is written to the memory at once \(if needed\)\.

**Note\.** All other initializations are called _dynamic initialization_, we do not review them here\.

**Note\.** A variable that was _zero\-initialized_, can be initialized again the "normal" way\. This will already be _dynamic initialization_ \(even if it happens before the _main_ method call\)\.

Let's review this example with both types of variable initialization:

```cpp
int foo()
{
  return 13;
}

const int test1 = 1 + 2 + 3 + 4;  // initialization with a const. expr.
const int test2 = 15 * test1 + 8; // initialization with a const. expr.
const int test3 = foo() + 5;      // zero-initialization
const int test4 = (1 < 2) ? 10 * test3 : 12345; // zero-initialization
const int test5 = (1 > 2) ? 10 * test3 : 12345; // initialization with
                                                // a const. expr.
```

You can use variables _test1_, _test2_, _test5_ as a template parameter, as an expression to the right of case in switch, etc\. You cannot do this with variables _test3_ and _test4_\.

As you can see from requirements for _constant expressions_ and from the example, there is transitivity\. If some part of an expression is not a _constant expression_, then the entire expression is not a _constant expression_\. Note that only those expression parts, that are evaluated, matter – which is why _test4_ and _test5_ fall into different groups\.

If there's nowhere for a _constant expression_ variable to get its address, the compiled program is allowed to skip reserving memory for the variable – so we will force the program to reserve the memory anyway\. Let's output variable values and their addresses:

```cpp
int main()
{
  std::cout << test1 << std::endl;
  std::cout << test2 << std::endl;
  std::cout << test3 << std::endl;
  std::cout << test4 << std::endl;
  std::cout << test5 << std::endl;

  std::cout << &test1 << std::endl;
  std::cout << &test2 << std::endl;
  std::cout << &test3 << std::endl;
  std::cout << &test4 << std::endl;
  std::cout << &test5 << std::endl;
}

izaron@izaron:~/cpp$ clang++ --std=c++98 a.cpp 
izaron@izaron:~/cpp$ ./a.out 
10
158
18
180
12345
0x402004
0x402008
0x404198
0x40419c
0x40200c
```

Now let's compile an object file and look at the table of symbols:

```cpp
izaron@izaron:~/cpp$ clang++ --std=c++98 a.cpp -c
izaron@izaron:~/cpp$ objdump -t -C a.o

a.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*  0000000000000000 a.cpp
0000000000000080 l     F .text.startup  0000000000000015 _GLOBAL__sub_I_a.cpp
0000000000000000 l     O .rodata        0000000000000004 test1
0000000000000004 l     O .rodata        0000000000000004 test2
0000000000000004 l     O .bss   0000000000000004 test3
0000000000000008 l     O .bss   0000000000000004 test4
0000000000000008 l     O .rodata        0000000000000004 test5
```

The compiler – its specific version for a specific architecture – placed a specific program's zero\-initialized variables into the [_\.bss_](https://en.wikipedia.org/wiki/.bss) section, and the remaining variables into the _\.rodata_ section\.

Before the launch, the bootloader loads the program in a way that the _\.rodata_ section ends up in the read\-only segment\. The segment is write\-protected at the OS level\.

Let's try to use _const\_cast_ to edit data stored at the variables' address\. The standard is not clear as to when using _const\_cast_ to write the result can cause undefined behavior\. At least, this does not happen when we remove _const_ from an object/a pointer to an object that is not fundamentally constant initially\. I\.e\. it's important to see a difference between _physical_ constancy and _logical_ constancy\.

The UB sanitizer catches UB \(the program crashes\) if we try to edit the _\.rodata_ variable\. There is no UB if we write to _\.bss_ or automatic variables\.

```cpp
const int &ref = testX;
const_cast<int&>(ref) = 13; // OK for test3, test4;
                            // SEGV for test1, test2, test5
std::cout << ref << std::endl;
```

Thus, some constant variables are "more constant" than others\. As far as we know, at that time, **there was no simple way** to check or monitor that a variable had been _initialized with a const\. expr_\.

## 0\-∞: Constant evaluator in compiler

To understand how constant expressions are evaluated during compilation, first you need to understand how the compiler is structured\.

Compilers are ideologically similar to each other\. I'll describe how Clang/LLVM evaluates constant expressions\. I copied basic information about this compiler from my [previous article](https://habr.com/en/post/576052/):

**\[SPOILER BLOCK BEGINS\]**

### Clang and LLVM

Many articles talk about Clang and LLVM\. To learn more about their history and general structure, you can read [this article](https://habr.com/en/company/huawei/blog/511854/) at Habr\.

The number of compilation stages depends on who explains the compiler's design\. The compiler's anatomy is multilevel\. At the most abstract level, the compiler looks like a fusion of three programs:

* **Front\-end:** converts the source code from C/C\+\+/Ada/Rust/Haskell/\.\.\. into [LLVM IR](https://llvm.org/docs/LangRef.html) – a special intermediate representation\. Clang is the front\-end for the C language family\.
* **Middle\-end:** LLVM IR is optimized depending on the settings\.
* **Back\-end**: LLVM IR is converted into machine code for the required platform \- x86/Arm/PowerPC/\.\.\.

For simple languages, one can easily write a compiler whose source code consists of [1000 lines](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/index.html) \- and get all the power of LLVM \- for this, you need to implement the front\-end\.

At a less abstract level is Clang's front\-end that performs the following actions \(not including the preprocessor and other "micro" steps\):

* [Lexical analysis](https://en.wikipedia.org/wiki/Lexical_analysis): converting characters into tokens, for example_ \[\]\(\) \{ return 13 \+ 37; \} are _converted to_ \(l\_square\) \(r\_square\) \(l\_paren\) \(r\_paren\) \(l\_brace\) \(return\) \(numeric\_constant:13\) \(plus\) \(numeric\_constant:37\) \(semi\) \(r\_brace\)_\.
* [Syntactic analysis](https://en.wikipedia.org/wiki/Parsing): creating an AST \(Abstract Syntax Tree\) \- that is, translating tokens from the previous paragraph into the following form: _\(lambda\-expr \(body \(return\-expr \(plus\-expr \(number 13\) \(number 37\)\)\)\)\)_\.
* Code generation: creating LLVM IR for specific AST\.

**\[SPOILER BLOCK ENDS\]**

So, evaluating constant expressions \(and entities that are closely related to them, like template instantiation\) takes place strictly in the C\+\+ compiler's \(Clang's in our case\) front\-end\. LLVM does not do such things\.

Let's tentatively call the micro\-service that evaluates constant expressions \(from the simplest ones in C\+\+98 to the most complicated ones in C\+\+23\) the **constant evaluator**\.

If, according to the standard, at some location in the code we expect a constant expression; and the expression that is there meets the requirements for a constant expression – Clang must be able to evaluate it in 100% of cases, right then and there\.

Constant expression restrictions have been constantly softened over the years, while Clang's constant evaluator kept getting more advanced – reaching the ability to manage the memory model\.

[Nine\-year\-old documentation](https://clang.llvm.org/docs/InternalsManual.html) describes how to evaluate constants in C\+\+98/03\. Since constant expressions were very simple then, they were evaluated with the conventional [constant folding](https://en.wikipedia.org/wiki/Constant_folding), through the abstract syntax tree \(AST\) analysis\. Since, in syntax trees, all arithmetic expressions are already broken apart into sub\-trees, evaluating a constant is a simple traversal of a sub\-tree\.

The constant evaluator's source code is located in [lib/AST/ExprConstant\.cpp](https://clang.llvm.org/doxygen/ExprConstant_8cpp_source.html) and had reached almost 16 thousand lines by the moment I was writing this article\. Over the years, it learned to interpret a lot of things, for example, loops \([EvaluateLoopBody](https://clang.llvm.org/doxygen/ExprConstant_8cpp.html)\) – all of this based on the syntax tree\.

The big difference of constant expressions from code executed in runtime \- they are required to not allow undefined behavior\. If the constant evaluator stumbles upon UB, compilation fails\.

```cpp
c.cpp:15:19: error: constexpr variable 'foo' must be initialized by a
                    constant expression
    constexpr int foo = 13 + 2147483647;
                  ^     ~~~~~~~~~~~~~~~
```

The constant evaluator is used not only for constant expressions, but also to look for potential bugs in the rest of the code\. This is a side benefit from this technology\. Here's how one can detect overflow in non\-constant code \(you can get a warning\):

```cpp
c.cpp:15:18: warning: overflow in expression; result is -2147483636
                      with type 'int' [-Winteger-overflow]
    int foo = 13 + 2147483647;
                 ^
```

## 2003: No need for macros

Changes to the standard occur through _proposals_\.

**\[SPOILER BLOCK BEGINS\]**

### Where are proposals located and what do they consist of?

All proposals to the standard are located at [open\-std\.org](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/)\. Most of them have detailed descriptions and are easy to read\. Usually, proposals contain the following:

* A short review of the area with links to standard sections;
* Current problems;
* The proposed solution to the problems;
* Suggested changes to the standard's text;
* Links to previous precursor proposals and previous revisions of the proposal;
* In advanced proposals – links to their implementation in a compiler's fork\. For the proposals that I saw, the authors implemented the proposal in Clang's fork\.

One can use the links to precursor proposals to track how each piece of C\+\+ evolved\.

Not all proposals from the archive were eventually accepted \(although some of them were used as a base for accepted proposals\), so it's important to understand that they describe some alternative version of C\+\+ of the time, and not a piece of modern C\+\+\.

Anyone can participate in the C\+\+ evolution – Russian\-speaking experts can use the [stdcpp\.ru](https://stdcpp.ru/en/about) website\.

**\[SPOILER BLOCK ENDS\]**

[\[N1521\] Generalized Constant Expressions](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2003/n1521.pdf) was proposed in 2003\. It points to a problem that if part of an expression is evaluated using a method call, then the expression is not considered a _constant expression_\. This forces developers – when they need a more or less complex constant expression – to overuse macros:

```cpp
#define SQUARE(X) ((X) * (X))
inline int square(int x) { return x * x; }
// ^^^ the macro and method definition
square(9)
std::numeric_limits<int>::max()
// ^^^ cannot be a part of a constant expression
SQUARE(9)
INT_MAX
// ^^^ theoretically can be a part of a constant expression
```

This is why the proposal suggests introducing a concept of _constant\-valued_ methods that would be allowed as part of a _constant expression_\. A method is considered _constant\-valued_ if this method is _inline_, non\-recursive, does not return _void_, and its body consists of a single _return expr;_ expression\. After substituting arguments \(that also include _constant expressions_\), the developer gets a _constant expression_\.

**Note\.** Looking ahead, the term _constant\-valued_ didn't catch on\.

```cpp
int square(int x) { return x * x; }         // constant-valued
long long_max(int x) { return 2147483647; } // constant-valued
int abs(int x) { return x < 0 ? -x : x; }   // constant-valued
int next(int x) { return ++x; }             // NOT constant-valued
```

Thus, all variables from the previous section \(_test1\-5_\) would become "fundamentally" constant, with no changes in code\.

The proposal believes that it's possible to go even further\. For example, this code should also compile:

```cpp
struct cayley
{
  const int value;
  cayley(int a, int b)
    : value(square(a) + square(b)) {}
  operator int() const { return value; }
};

std::bitset<cayley(98, -23)> s; // eq. to bitset<10133>
```

The reason for this is, the _value_ variable is "fundamentally constant", because it was initialized in a constructor through a _constant expression_ with two calls of the _constant valued_ method\. Consequently, according to the proposal's general logic, the code above can be transformed to something like this \(by taking variables and methods outside of the structure\):

```cpp
// imitating constructor calls: cayley::cayley(98, -23) and operator int()
const int cayley_98_m23_value = square(98) + square(-23);

int cayley_98_m23_operator_int()
{
  return cayley_98_m23_value;
}

// creating a bitset
std::bitset<cayley_98_m23_operator_int()> s; // eq. to bitset<10133>
```

Proposals do not usually focus deeply on the details of how compilers can implement these proposals\. This proposal says that there should not be any difficulties in implementing it \- on just needs to slightly alter constant folding, which exists in most compilers\.

**Note\.** However, proposals cannot exist in isolation from compilers – proposals impossible to be implemented in a reasonable time are unlikely to be approved\.

As with variables, a developer cannot check whether a method is _constant\-valued_\.

## 2006\-2007: When it all becomes clear

Luckily, in three years, over the next revisions of this proposal \([\[N2235\]](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2235.pdf)\), it became clear that the feature would have brought too much unclarity and this was not good\. Then one more item was added to the list of problems \- the inability to monitor initialization:

```cpp
struct S
{
  static const int size;
};

const int limit = 2 * S::size; // dynamic initialization
const int S::size = 256; // constant expression initialization
const int z = std::numeric_limits<int>::max(); // dynamic initialization
```

The programmer intended _limit_ to be initialized by a constant expression, but this does not happen, because _S::size_ is defined "too late", after _limit_\. If it were possible to request the required initialization type, the compiler would have produced an error\.

Same with methods\. _Constant\-valued_ methods were renamed to _constant\-expression_ methods\. The requirements for them remained the same, but now, in order to use these methods in a _constant expression_, it was necessary to declare them with the _constexpr_ keyword\. The compilation would fail if the method body is not the correct _return expr;_\.

The compilation would also fail and produce the _constexpr function never produces a constant expression_ error if a _consexpr_ method cannot be used in a constant expression\. This is necessary to help the developer make sure that a method can be potentially used in a _constant expression_\.

The proposal suggests to tag some methods from the standard library \(for example, from _std::numeric\_limits_\) as _constexpr_, if they meet the requirements for _constexpr_ methods\.

Variables or class members can also be declared as _constexpr_ \- then the compilation will fail if a variable is not initialized through a _constant expression_\.

At that time, it was decided to keep the new word's compatibility with variables, implicitly initialized through a _constant expression_, but without the _constexpr_ word\. Which means the code below worked \(looking ahead, this code with _\-\-std\=c\+\+11_ does not compile – and it is possible that this code never started to work at all\):

```cpp
const double mass = 9.8;
constexpr double energy = mass * square(56.6); // OK, although mass 
                                               // was not defined 
                                               // with constexpr
extern const int side;
constexpr int area = square(side); // error: square(side) is not
                                   // a constant expression
```

_Constant\-expression_ constructors for user\-defined types were also legalized\. This constructor must have an empty body and initialize its members with _constexpr expressions_ if a developer creates a _constexpr_ object of this class\.

The implicitly\-defined constructor is marked as _constexpr_ whenever possible\. Destructors for _constexpr_ objects must be trivial, since non\-trivial ones usually change something in the context of a running program that does not exist as such in _constexpr_ evaluations\.

Example of a class with _constexpr_ members, from the proposal:

```cpp
struct complex
{
  constexpr complex(double r, double i) : re(r), im(i) { }

  constexpr double real() { return re; }
  constexpr double imag() { return im; }

private:
  double re;
  double im;
};

constexpr complex I(0, 1); // OK -- literal complex
```

The proposal called objects like the I object _user\-defined literals_\. A "literal" is something like a basic entity in C\+\+\. "Simple" literals \(numbers, characters, etc\) are passed as they are into assembler commands\. String literals are stored in a section similar to _\.rodata_\. Similarly, user\-defined literals also have their own place somewhere there\.

Now, aside from numbers and enumerations, _constexpr_ variables could be represented by [literal types](https://en.cppreference.com/w/cpp/named_req/LiteralType) introduced in this proposal \(so far without _reference types_\)\. A literal type is a type that can be passed to a _constexpr_ function, and/or modified and/or returned from it\. These types are fairly simple\. Compilers can easily support them in the constant evaluator\.

The _constexpr_ keyword became a specifier that compilers require – similarly to _override_ in classes\. After the proposal was discussed, it was decided to avoid creating a new [storage class](https://en.cppreference.com/w/cpp/language/storage_duration) \(although that would have made sense\) and a new [type qualifier](https://en.cppreference.com/w/cpp/language/cv)\. Using it with function arguments was not allowed so as not to overcomplicate the rules for overload resolution\.

## 2007: First constexpr for data structures

That year, the [\[N2349\] Constant Expressions in the Standard Library](http://open-std.org/JTC1/SC22/WG21/docs/papers/2007/n2349.pdf) proposal was submitted\. It tagged as _constexpr_ some functions and constants, as well as some container functions, for example:

```cpp
template<size_t N>
class bitset
{
  // ...
  constexpr bitset();
  constexpr bitset(unsigned long);
  // ...
  constexpr size_t size();
  // ...
  constexpr bool operator[](size_t) const;
};
```

Constructors initialize class members through a _constant expression_, other methods contain _return expr;_ in their body\. This return expression meets the current requirements\.

Over half of the proposals about _constexpr_ talk about tagging some functions from the standard library as _constexpr_\. There are always more proposals like this after each new step of the _constexpr_ evolution\. And almost always they are not very interesting\.

## 2008: Recursive constexpr methods

_constexpr_ methods were not initially intended to be made recursive, mainly because there were no convincing arguments in favor of recursion\. Then the restriction was lifted, which was noted in [\[N2826\] Issues with Constexpr](http://open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2826.html)\.

```cpp
constexpr unsigned int factorial( unsigned int n )
{
  return n==0 ? 1 : n * factorial( n-1 );
}
```

Compilers have a certain limit of nested calls\. Clang, for example, can process a maximum of 512 nested calls\. If this number is exceeded, the compiler won't evaluate the expression\.

Similar limits exist for template instantiation \(for example, if we used templates instead of _constexpr_ to do compile\-time evaluations\)\.

## 2010: "const T&" as arguments in constexpr methods

At this time, many functions cannot be tagged as _constexpr_ because of references to constants in the arguments\. Parameters are passed by value – i\.e\. are copied – to all _constexpr_ methods\.

```cpp
template< class T >
constexpr const T& max( const T& a, const T& b ); // does not compile

constexpr pair(); // can use constexpr
pair(const T1& x, const T2& y); // cannot use constexpr
```

Proposal [\[N3039\] Constexpr functions with const reference parameters \(a summary\)](http://open-std.org/JTC1/SC22/WG21/docs/papers/2010/n3039.pdf) allows constant references in function arguments and as a return value\.

This is a dangerous change: before that, the constant evaluator dealt with simple expressions and _constexpr_ variables \(a literal\-class object – essentially, a set of _constexpr_ variables\); but the introduction of references breaks through the "fourth wall", because this concept refers to the memory model that the evaluator does not have\.

Overall, working with references or pointers in _constant expressions_ turns a C\+\+ compiler into a C\+\+ interpreter, so various limitations are set\.

If the constant evaluator can process a function with a type _T_ argument, processing this function with the const _T&_ is also possible \- if the constant evaluator "imagines" that a "temporary object" is created for this argument\.

Compilers cannot compile code that requires more or less complicated work or that tries to break something\.

```cpp
template<typename T> constexpr T self(const T& a) { return *(&a); }
template<typename T> constexpr const T* self_ptr(const T& a) { return &a; }

template<typename T> constexpr const T& self_ref(const T& a)
{
  return *(&a);
}

template<typename T> constexpr const T& near_ref(const T& a)
{
  return *(&a + 1);
}

constexpr auto test1 = self(123);     // OK
constexpr auto test2 = self_ptr(123); // FAIL, pointer to temporary is not
                                      // a constant expression
constexpr auto test3 = self_ref(123); // OK
constexpr auto tets4 = near_ref(123); // FAIL, read of dereferenced
                                      // one-past-the-end pointer is not
                                      // allowed in a constant expression
```

## 2011: static\_assert in constexpr methods

Proposal [\[N3268\] static\_assert and list\-initialization in constexpr functions](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3268.htm) introduces the ability to write "static" declarations that do not affect how function operate: _typedef_, _using_, _static\_assert_\. This slightly untightens the nuts for _constexpr_ functions\.

## 2012: \(Almost\) any code in constexpr functions

In 2012, there was a big leap forward with the proposal [\[N3444\] Relaxing syntactic constraints on constexpr functions](http://open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3444.html)\. There are many simple functions that are preferable to be executed at compile\-time, for example, the _a^n_ power:

```cpp
// Compute a to the power of n
int pow(int a, int n)
{
  if (n < 0)
    throw std::range_error("negative exponent for integer power");
  if (n == 0)
    return 1;
  int sqrt = pow(a, n/2);
  int result = sqrt * sqrt;
  if (n % 2)
    return result * a;
  return result;
}
```

However, in order to make its _constexpr_ variant, developers have to go out of their way and write in a functional style \(remove local variables and _if_\-statements\):

```cpp
constexpr int pow_helper(int a, int n, int sqrt)
{
  return sqrt * sqrt * ((n % 2) ? a : 1);
}

// Compute a to the power of n
constexpr int pow(int a, int n)
{
  return (n < 0)
    ? throw std::range_error("negative exponent for integer power")
    : (n == 0) ? 1 : pow_helper(a, n, pow(a, n/2));
}
```

This is why the proposal wants to allow adding any code to _constexpr_ functions \- with some restrictions:

* It's impossible to use loops \(_for_/_while_/_do_/range\-based for\), because variable changes are not allowed in constant expressions;
* _switch_ and _goto_ are forbidden so that the constant evaluator does not simulate complex control flows;
* As with the old restrictions, functions should theoretically have a set of arguments that enable you to use these functions in constant expressions\. Otherwise, the compiler assumes a function was marked as _constexpr_ accidentally, and the compilation will fail with _constexpr function never produces a constant expression_\.

_Local_ variables \- if they have the literal type \- can be declared within these functions\. If these variables are initialized with a constructor, it must be a _constexpr_ constructor\. This way, when processing a _constexpr_ function with specific arguments, the constant evaluator can create a "background" _constexpr_ variable for each local variable, and then use these "background" variables to evaluate other variables that depend on the variables that have just been created\.

**Note\.** There can't be too many of such variables because of a strict limitation on the depth of the nested calls\.

You can declare _static_ variables in methods\. These variables may have a non\-literal type \(in order to, for example, return references to them from a method; the references are, however, of the literal type\)\. However, these variables should not have the _dynamic realization_ \(i\.e\. at least one initialization should be a _zero initialization_\)\. The sentence gives an example where this feature could be useful \(getting a link to a necessary object at compile\-time\):

```cpp
constexpr mutex &get_mutex(bool which)
{
  static mutex m1, m2; // non-const, non-literal, ok
  if (which)
    return m1;
  else
    return m2;
}
```

Declaring types \(_class_, _enum_, etc\.\) and returning _void_ was also allowed\.

## 2013: \(Almost\) any code allowed in constexpr functions ver 2\.0 Mutable Edition

However, the Committee decided that supporting loops \(at least _for_\) in _constexpr_ methods is a must\-have\. In 2013 an amended version of the [\[N3597\] Relaxing constraints on constexpr functions](http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3597.html) proposal came out\.

It described four ways to implement the "_constexpr_ _for_" feature\.

One of the choices was very far from the "general C\+\+"\. It involved creating a completely new construction for iterations that would the _constexpr_ code's functional style of the time\. But that would have created a new sub language \- the functional style _constexpr C\+\+\._

The choice closest to the "general C\+\+" was not to replace quality with quantity\. Instead, the idea was to try to support in _constexpr_ a broad subset of C\+\+ \(ideally, all of it\)\. **This option was selected\.** This significantly affected _constexpr_'s subsequent history\.

This is why there was a need for **object mutability** within _**constexpr**_ **evaluations**\. According to the proposal, an object created within a _constexpr_ expression, can now be changed during the evaluation process \- until the evaluation process or the object's [lifetime](http://eel.is/c++draft/basic.life) ends\.

These evaluations still take place inside their "sandbox", nothing from the outside affects them\. So, in theory, evaluating a _constexpr_ expression with the same arguments will produce the same result \(not counting the float\- and double\- calculation errors\)\.

For a better understanding I copied a code snippet from the proposal:

```cpp
constexpr int f(int a)
{
  int n = a;
  ++n;                  // '++n' is not a constant expression
  return n * a;
}

int k = f(4);           // OK, this is a constant expression.
                        // 'n' in 'f' can be modified because its lifetime
                        // began during the evaluation of the expression.

constexpr int k2 = ++k; // error, not a constant expression, cannot modify
                        // 'k' because its lifetime did not begin within
                        // this expression.

struct X
{
  constexpr X() : n(5)
  {
    n *= 2;             // not a constant expression
  }
  int n;
};

constexpr int g()
{
  X x;                  // initialization of 'x' is a constant expression
  return x.n;
}

constexpr int k3 = g(); // OK, this is a constant expression.
                        // 'x.n' can be modified because the lifetime of
                        // 'x' began during the evaluation of 'g()'.
```

Let me note here, that at the time being the code below is compiled:

```cpp
constexpr void add(X& x)
{
  x.n++;
}

constexpr int g()
{
  X x;
  add(x);
  return x.n;
}
```

Right now, a significant part of C\+\+ can work within _constexpr_ functions\. Side effects are also allowed \- if they are local within a _constexpr_ evaluation\. The constant evaluator became more complex, but still could handle the task\.

## 2013: Legendary const methods and popular constexpr methods

The _constexpr_ class member functions are currently automatically marked as _const_ functions\.

Proposal [\[N3598\] constexpr member functions and implicit const](http://open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3598.html) notices that it's not necessary to implicitly make the _constexpr_ class member functions _const_ ones\.

This has become more relevant with mutability in _constexpr_ evaluations\. However, even before, this had been limiting the use of the same function in the _constexpr_ and non\-_constexpr_ code:

```cpp
struct B
{
  constexpr B() : a() {}
  constexpr const A &getA() const /*implicit*/ { return a; }
  A &getA() { return a; } // code duplication
  A a;
};
```

Interestingly, the proposal gave a choice of three options\. The second option was chosen in the end:

1. Status quo\. Cons: code duplication\.
1. _constexpr_ will not implicitly mean _const_\. Cons: it breaks [ABI](https://cor3ntin.github.io/posts/abi/) — const is a part of the [mangled method name](https://en.wikipedia.org/wiki/Name_mangling)\.
1. Adding a new qualifier and writing _constexpr A &getA\(\) mutable \{ return a; \}_\. Cons: a new buzzword at the end of the declaration\.

## 2015\-2016: Syntactic sugar for templates

In template metaprogramming, functions are usually overloaded if the body requires different logic depending on a type's properties\. Example of scary code:

```cpp
template <class T, class... Args> 
enable_if_t<is_constructible_v<T, Args...>, unique_ptr<T>> 
make_unique(Args&&... args) 
{
    return unique_ptr<T>(new T(forward<Args>(args)...));
}  

template <class T, class... Args>  
enable_if_t<!is_constructible_v<T, Args...>, unique_ptr<T>>
make_unique(Args&&... args) 
{
    return unique_ptr<T>(new T{forward<Args>(args)...});
}
```

Proposal [\[N4461\] Static if resurrected](http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4461.html) introduces the _static\_if_ expression \(borrowed from the D language\) to make code less scary:

```cpp
template <class T, class... Args> 
unique_ptr<T>
make_unique(Args&&... args) 
{
  static_if (is_constructible_v<T, Args...>)
  {
    return unique_ptr<T>(new T(forward<Args>(args)...));
  }
  else
  {
    return unique_ptr<T>(new T{forward<Args>(args)...});
  }
}
```

This C\+\+ fragment has a rather mediocre relation to _constexpr_ expressions and works in a different scenario\.  But _static\_if_ in further revisions was renamed:

```cpp
constexpr_if (is_constructible_v<T, Args...>)
{
  return unique_ptr<T>(new T(forward<Args>(args)...));
}
constexpr_else
{
  return unique_ptr<T>(new T{forward<Args>(args)...});
}
```

Then some more renaming:

```cpp
constexpr if (is_constructible_v<T, Args...>)
{
  return unique_ptr<T>(new T(forward<Args>(args)...));
}
constexpr_else
{
  return unique_ptr<T>(new T{forward<Args>(args)...});
}
```

And the final version:

```cpp
if constexpr (is_constructible_v<T, Args...>)
{
  return unique_ptr<T>(new T(forward<Args>(args)...));
}
else
{
  return unique_ptr<T>(new T{forward<Args>(args)...});
}
```

## 2015: Constexpr lambdas

A very good proposal, [\[N4487\] Constexpr Lambda](http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4487.pdf), works scrupulously through the use of the closure type in _constexpr_ evaluations \(and supported the forked Clang\)\.

If you want to understand how it's possible to have _constexpr_ lambdas, you need to understand how they work from the inside\. There is an article about [the history of lambdas](https://www.cppstories.com/2019/02/lambdas-story-part1/?m=1) that describes how proto\-lambdas already existed in C\+\+03\. Today's lambda expressions have a similar class hidden deep inside the compiler\.

**\[SPOILER BLOCK BEGINS\]**

### Proto\-lambda for \[\]\(int x\) \{ std::cout << x << std::endl; \}

```cpp
#include <iostream>
#include <algorithm>
#include <vector>

struct PrintFunctor
{
  void operator()(int x) const
  {
    std::cout << x << std::endl;
  }
};

int main()
{
  std::vector<int> v;
  v.push_back(1);
  v.push_back(2);
  std::for_each(v.begin(), v.end(), PrintFunctor());
}
```

**\[SPOILER BLOCK ENDS\]**

If all the captured variables are literal types, then closure type is also proposed to be considered a literal type, and _operator\(\)_ is marked _constexpr_\. The working example of _constexpr_ lambdas:

```cpp
constexpr auto add = [] (int n, int m)
{
  auto L = [=] { return n; };
  auto R = [=] { return m; };
  return [=] { return L() + R(); };
};

static_assert(add(3, 4)() == 7, "");
```

## 2017\-2019: Double standards

Proposal [\[P0595\] The constexpr Operator](http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0595r0.html) considers the possibility of "knowing" inside the function where the function is being executed now \- in a constant evaluator or in runtime\. The author proposed calling _constexpr\(\)_ for this, and it will return _true_ or _false_\.

```cpp
constexpr double hard_math_function(double b, int x)
{
  if (constexpr() && x >= 0)
  {
    // slow formula, more accurate (compile-time)
  }
  else
  {
    // quick formula, less accurate (run-time)
  }
}
```

Then the operator was replaced with the "magic" function _std::is\_constant\_evaluated\(\)_ \([\[P0595R2\]](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0595r2.html)\) and was adopted by the C\+\+20 standard in this form\.

If the proposal has been developed for a long time, then the authors sometimes do its "rebase" \(similar to projects in git/svn\), bringing it in line with the updated state\.

Same thing here — the authors of [\[P1938\] if consteval](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1938r0.html) \(I'll talk about _consteval_ later\) found that it's better to create a new entry:

```cpp
if consteval { }
if (std::is_constant_evaluated()) { }
// ^^^ similar entries
```

This decision was made in C\+\+23 — [link to the vote](https://github.com/cplusplus/papers/issues/677)\.

## 2017\-2019: We need to go deeper

In the _constexpr_ functions during the _constexpr_ evaluations we cannot yet use the debugger and output logs\. Proposal [\[P0596\] std::constexpr\_trace and std::constexpr\_assert](http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0596r0.html) considers the introduction of special functions for these purposes\.

The proposal was favorably accepted \([link to the vote](https://github.com/cplusplus/papers/issues/602)\) but has not yet been finalized\.

## 2017: The evil twin of the standard library

At this moment, _std::vector_ \(which is desirable to have in compile\-time\), cannot work in _constexpr_ evaluations, It's mainly due to the unavailability of _new/delete_ operators there\.

The idea of allowing the _new_ and _delete_ operators into the constant evaluator looked too ambitious\. Thus, a rather strange proposal [\[P0597\] std::constexpr\_vector](http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0597r0.html) considers introducing the magic _std::constexpr\_vector<T\>_\.

It is the opposite of _std::vector<T\>_ — can be created and modified only during _constexpr_ evaluations\.

```cpp
constexpr constexpr_vector<int> x;           // Okay.
constexpr constexpr_vector<int> y{ 1, 2, 3 };// Okay.
const constexpr_vector<int> xe;              // Invalid: not constexpr
```

It is not described how the constant evaluator should work with memory\. [@antoshkka](https://habr.com/users/antoshkka) and [@ZaMaZaN4iK](https://habr.com/users/zamazan4ik) \(the authors of many proposals\) in [\[P0639R0\] Changing attack vector of the constexpr\_vector](http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0639r0.html) detected many cons of this approach\. They proposed changing the work direction towards an abstract magic _constexpr allocator_ that doesn't duplicate the entire standard library\.

## 2017\-2019: Constexpr gains memory

The [Constexpr ALL the thing\!](https://youtu.be/HMB9oXFobJc) presentation demonstrates an example of a _constexpr_ library to work with JSON objects\. The same thing, but in paper form, is in [\[P0810\] constexpr in practice](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0810r0.pdf):

```cpp
constexpr auto jsv
    = R"({
          "feature-x-enabled": true,
          "value-of-y": 1729,
          "z-options": {"a": null,
                        "b": "220 and 284",
                        "c": [6, 28, 496]}
         })"_json;

if constexpr (jsv["feature-x-enabled"])
{
  // code for feature x
}
else
{
  // code when feature x turned off
}
```

The authors suffered greatly from the inability to use STL containers and wrote the _std::vector_ and _std::map_ analogues\. Inside, these analogues have _std::array_ that can work in _constexpr_\.

Proposal [\[P0784\] Standard containers and constexpr](http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p0784r7.html) studies the possibility of inputting STL containers in _constexpr_ evaluations\.

**Note\.** It's important to know what an _allocator_ is\. STL containers work with memory through it\. What kind of an allocator — is specified through the [tempte argument](https://en.cppreference.com/w/cpp/container/vector)\. If you want to get into the topic, read [this article](https://habr.com/en/post/505632/)\.

What's stopping us from allowing STL containers to be in _constexpr_ evaluations? There are three problems:

1. Destructors cannot be declared _constexpr_\. For _constexpr_ objects it must be trivial\.
1. Dynamic memory allocation/deallocation is not available\.
1. _placement\-new_ is not available for calling the constructor in the allocated memory\.

**First problem\.** It was quickly fixed — the proposal authors discussed this problem with the developers of the MSVC\+\+ frontend, GCC, Clang, EDG\. The developers confirmed that the restriction can be relaxed\. Now we can require from literal types to have a _constexpr_ destructor, not the strictly trivial one\.

**Second problem\.** Working with memory is not very easy\. The constant evaluator _is obliged_ to catch undefined behavior in any form\. If the constant evaluator finds undefined behavior, it should stop compilation\.

This means that we should track not only objects, but also their "metadata" that keep everything in check and don't let us crash the program\. A couple of examples of such metadata:

* Information about which field in _union_ is active \([\[P1330\]](http://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1330r0.pdf)\)\. An example of undefined behavior: writing to a member of inactive field\.
* A rigid connection between a pointer or a reference and a corresponding previously created object\. An example of undefined behavior: infinite set\.

Because of this, it's pointless to use such methods:

```cpp
void* operator new(std::size_t);
```

The reason is, there's no justification to bring _void\*_ to _T\*_\. In short, a new reference/pointer can either start pointing to an existing object or be created "simultaneously" with it\.

That's why there are two options for working with memory that are acceptable in _constexpr_ evaluations:

1. Simple new and delete expressions: _int\* i \= new int\(42\)_;
1. Using a standard allocator: [std::allocator](https://en.cppreference.com/w/cpp/memory/allocator) \(it was slightly filed\)\.

**Third problem\.** Standard containers separate memory allocations and the construction of objects in this memory\. We figured out the problem with allocations — it is possible to provide it with a condition for metadata\.

Containers rely on [std::allocator\_traits](https://en.cppreference.com/w/cpp/memory/allocator_traits), for construction — on its [construct](https://en.cppreference.com/w/cpp/memory/allocator_traits/construct) method\. Before the proposal it has the following form:

```cpp
template< class T, class... Args >
static void construct( Alloc& a, T* p, Args&&... args )
{
  ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...);
  // ^^^ placement-new forbidden in constexpr evaluations
}
```

It cannot be used due to casting to _void\*_ and _placement\-new_ \(forbidden in _constexpr_ in general form\)\. In the proposal it was transformed into

```cpp
template< class T, class... Args >
static constexpr void construct( Alloc& a, T* p, Args&&... args )
{
  std::construct_at(p, std::forward<Args>(args)...);
}
```

[std::construct\_at](https://en.cppreference.com/w/cpp/memory/construct_at) is a function that works similarly to the old code in runtime \(with a cast to _void\*_\)\. In _constexpr_ evaluations:

\.∧＿∧

\( ･ω･｡\)つ━☆・\*。

⊂　 ノ 　　　・゜\+\.

しーＪ　　　°。\+ \*´¨\)

　　　　　　　　　\.· ´¸\.·\*´¨\) ¸\.·\*¨\)

　　　　　　　　　　\(¸\.·´ \(¸\.·'\* ☆ Whoosh – and it just works\! ☆

The compiler constant evaluator will process it in a special way: apparently, by calling constructor from object connected to _T\*p_\.

It's enough to make it possible to use containers in _constexpr_ evaluations\.

At first, there were some restrictions on allocated memory\. It should have been deallocated within the same _constexpr_ evaluation without going beyond the "sandbox"\.

This new type of memory allocation is called _transient constexpr allocations_\. _Transient_ also means "temporal" or "short\-lived"\.

The proposal also had a piece about _non\-transient allocation_\. It proposed releasing not all allocated memory\. The unallocated memory "falls out" of the sandbox and would be converted to static storage — i\.e\. in the _\.rodata_ section\. However, the committee considered this possibility "_too brittle_" for many reasons and has not accepted it yet\.

The rest of the proposal was accepted\.

## 2018: Catch me if you can

Proposal [\[P1002\] Try\-catch blocks in constexpr functions](http://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1002r1.pdf) brings try\-catch blocks into _constexpr_ evaluations\.

This proposal is a bit confusing — _throw_ was banned in _constexpr_ evaluations at that moment\. This means the _catch_ code fragment never runs\.

Judging by the document, this was introduced to mark all the _std::vector_ functions as _constexpr_\. In libc\+\+ \(STL implementation\) a try\-catch block is used in the _vector::insert_ method\.

## 2018: I said constexpr\!

From personal experience I know the duality of the _constexpr_ functions \(can be executed at compile\-time and runtime\) leads to the fact that evaluations fall into runtime when you least expect it — [code example](https://godbolt.org/z/f8xY7T9xn)\. If you want to guarantee the right stage, you have to be creative — [code example](https://godbolt.org/z/9Prs41nhj)\.

Proposal [\[P1073\] constexpr\! functions](http://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1073r0.html) introduces new keyword _constexpr\!_ for functions that should work only at compile\-time\. These functions are called _immediate_ methods\.

```cpp
constexpr! int sqr(int n)
{
  return n*n;
}

constexpr int r = sqr(100);  // Okay.
int x = 100;
int r2 = sqr(x);             // Error: Call does not produce
                             // a constant.
```

If there's a _possibility_ that variables unknown at the compilation stage may get into _constexpr\!_ \(which is normal for _constexpr_ functions\), then the program won't compile:

```cpp
constexpr! int sqrsqr(int n)
{
  return sqr(sqr(n)); // Not a constant expression at this point,
}                     // but that's okay.

constexpr int dblsqr(int n)
{
  return 2 * sqr(n); // Error: Enclosing function is not
}                    // constexpr!.
```

You cannot take a pointer/link to a _constexpr\!_ function\. The compiler backend does not necessarily \(and does not need to\) know about the existence of such functions, put them in symbol tables, etc\.

In further revisions of this proposal, _constexpr\!_ was replaced by _consteval_\.

The difference between _constexpr\!_ and_ consteval_ is obvious\. In the second case there's no fallbacks into runtime — [example with constexpr](https://godbolt.org/z/f8xY7T9xn); [example with consteval](https://godbolt.org/z/x6ds7vM8r)\.

## 2018: Too radical constexpr

At that moment a lot of proposals were about adding the _constexpr_ specifier to various parts of the standard library\. We do not discuss them in this article since it's the same template\.

Proposal [\[P1235\] Implicit constexpr](http://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1235r0.pdf) suggests marking all functions, that have a definition, as _constexpr_\. But we can ban executing a function in compile\-time:

1. <no specifier\> — a method is marked by _constexpr_, if possible\.
1. _constexpr_ — works as it works now;
1. _constexpr\(false\)_ — cannot be called at compile\-time;
1. _constexpr\(true\)_ — can be called only at compile\-time, i\.e\. similar to _constexpr\!/consteval_\.

This proposal wasn't accepted — [link to the vote](https://github.com/cplusplus/papers/issues/292)\.

## 2020: Long\-lasting constexpr memory

As already discussed, after accepting proposal [\[P0784\] Standard containers and constexpr](http://open-std.org/JTC1/SC22/WG21/docs/papers/2019/p0784r7.html), it became possible to allocate memory in _constexpr_ evaluations\. However, the memory must be freed before the end of a _constexpr_ evaluation\. These are so\-called _transient constexpr allocations_\.

Thus, you cannot create top\-level _constexpr_ objects of almost all STL containers and many other classes\.

By "top\-level object" I mean the result of the whole _constexpr_ evaluation, for example:

```cpp
constexpr TFoo CalcFoo();
constexpr TFoo FooObj = CalcFoo();
```

Here the _CalcFoo\(\) _call starts a _constexpr_ evaluation, and _FooObj_ \- its result and a _top\-level_ _constexpr_ object\.

Proposal [\[P1974\] Non\-transient constexpr allocation using propconst](http://open-std.org/JTC1/SC22/WG21/docs/papers/2020/p1974r0.pdf) finds a way to solve the problem\. To my mind, this is the most interesting proposal of all I gave in this article\. It deserves a separate article\. This proposal was given a green light and it's developing — [a link to the ticket](https://github.com/cplusplus/papers/issues/867)\. I'll retell it here in an understandable form\.

What's stopping us from having _non\-transient allocations_? Actually, the problem is not to stuff chunks of memory into static storage \(_\.bss_/_\.rodata_/their analogues\), but to check that the whole scheme has a clear **consistency**\.

Let's assume that we have a certain _constexpr_ object\. Its construction \(more precisely, "evaluation"\) was provoked by _non\-transient allocations_\. This means that theoretical deconstruction of this object \(i\.e\. calling its destructor\) should release all _non\-transient_ memory\. If calling the destructor would not release memory, then this is bad\. There's no **consistency**, and a compilation error needs to be issued\.

In other words, here's what a constant evaluator should do:

1. After seeing a request for a _constexpr_ evaluation, execute it;
1. As a result of the evaluation, get an object that hides a bundle of _constexpr_ variables of a literal type\. Also get a certain amount of unallocated memory \(_non\-transient allocations_\);
1. _Imitate_ a destructor call on this object \(without actually calling it\)\. Check that this call _would release_ all _non\-transient_ memory;
1. If all checks were successful, then **consistency** proven\. _Non\-transient allocations_ can be moved to static storage\.

This seems logical and let's assume that it all was implemented\. But then we'd get a problem with similar code with _non\-transient_ memory\. The standard won't prohibit changing the memory and then checking for a destructor call will be pointless:

```cpp
constexpr unique_ptr<unique_ptr<int>> uui
    = make_unique<unique_ptr<int>>(make_unique<int>());

int main()
{
  unique_ptr<int>& ui = *uui;
  ui.reset();
}
```

**Note\.** In reality, such code would be rebuffed by the OS for trying to write to a read\-only RAM segment, but this is _physical_ constancy\. Code should have _logical_ constancy\.

Marking _constexpr_ for objects entails marking them as _const_\. All their members also become _const_\.

However, if an object has a member of pointer type, it's bad — you won't be able to make it _point_ to another object\. But you can change the object to which it _points_\.

Pointer types have two orthogonal constancy parameters:

1. Is it possible to start pointing to another object?
1. Is it possible to change the object pointed to?

In the end, we get 4 variants with different properties\. _OK_ — the string compiles, _FAIL_ \- it doesn't:

```cpp
int dummy = 13;

int *test1 { nullptr };
test1 = &dummy; // OK
*test1 = dummy; // OK

int const *test2 { nullptr };
test2 = &dummy; // OK
*test2 = dummy; // FAIL

int * const test3 { nullptr };
test3 = &dummy; // FAIL
*test3 = dummy; // OK

int const * const test4 { nullptr };
test4 = &dummy; // FAIL
*test4 = dummy; // FAIL
```

"Normal" _const_ leads to the third option, but _constexpr_ needs the fourth one\! I\.e\. it needs so\-called _deep\-const_\.

The proposal based on a couple of old proposals suggests introducing new [cv\-qualifier](https://en.cppreference.com/w/cpp/language/cv) _propconst_ \(_propagating const_\)\. 

This qualifier will be used with pointer/reference types:

```cpp
T propconst *
T propconst &
```

Depending on the _T_ type, the compiler will either convert this word into _const_ or delete it\. The first case is if _T_ is constant, the second if it's not\.

```cpp
int propconst * ---> int *
int propconst * const ---> int const * const
```

The proposal contains a table of _propconst_ conversion in different cases:

Thus, the _constexpr_ objects could acquire full logical consistency \(_deep\-const_\):

```cpp
constexpr unique_ptr<unique_ptr<int propconst> propconst> uui =
  make_unique<unique_ptr<int propconst> propconst>(
    make_unique<int propconst>()
  );

int main()
{
  // the two lines below won't compile
  unique_ptr<int propconst>& ui1 = *uui;
  ui1.reset();

  // the line below compiles
  const unique_ptr<int propconst>& ui2 = *uui;
  // the line below won't compile
  ui2.reset();
}

// P.S. This entry has not yet been adopted by the Committee.
// I hope they'll do better
```

## 2021: Constexpr classes

With the advent of fully _constexpr_ classes, including _std::vector_, _std::string_, _std::unique\_ptr_ \(in which all functions are marked as _constexpr_\) there is a desire to say "mark all functions of the class as _constexpr_"\.

This makes proposal [\[P2350\] constexpr class](http://open-std.org/JTC1/SC22/WG21/docs/papers/2021/p2350r1.pdf):

```cpp
class SomeType
{
public:
  constexpr bool empty() const { /* */ }
  constexpr auto size() const { /* */ }
  constexpr void clear() { /* */ }
  // ...
};
// ^^^ BEFORE

class SomeType constexpr
{
public:
  bool empty() const { /* */ }
  auto size() const { /* */ }
  void clear() { /* */ }
  // ...
};
// ^^^ AFTER
```

I have an interesting story about this proposal\. I didn't know about its existence and had an idea on [stdcpp\.ru](https://stdcpp.ru/en/about) to propose the same thing: [a link to the ticket \[RU\]](https://github.com/cpp-ru/ideas/issues/479) \(which is not needed now\)\.

Many almost identical proposals to the standard may appear almost simultaneously\. This speaks in favor of [the concept of multiple discovery](https://en.wikipedia.org/wiki/Multiple_discovery): ideas are floating in the air and it doesn't matter who proposes them\. If the community is big enough, the natural evolution occurs\.

## 2019\-∞: Constant interpreter in the compiler

_constexpr_ evaluations can be very slow, because the constant evaluator on the syntax tree has evolved iteratively \(starting with constant folding\)\. Now the constant evaluator is doing a lot of unnecessary things that could be done more efficiently\.

Since 2019, Clang has been developing [ConstantInterpeter](https://clang.llvm.org/docs/ConstantInterpreter.html)\. In future it may replace constant evaluator in the syntax tree\. It is quite interesting and deserves a separate article\.

The idea of ConstantInterpeter is that you can generate bytecode on the base of a syntax tree and execute it on the interpreter\. Interpreter supports the stack, call frames and a memory model \(with metadata mentioned above\)\.

The documentation for ConstantInterpeter is good\. There are also a lot of interesting things in [the video](https://youtu.be/LgrgYD4aibg) of the interpreter creator at the LLVM developers conference\.

## What else to look?

If you want to expand your understanding further, you can watch these wonderful talks from the experts\. In each talk authors go beyond the story about _constexpr_\. This may be constructing a _constexpr_ library; a story about the use of _constexpr_ in the future [reflexpr](https://en.cppreference.com/w/cpp/experimental/reflect); or the story about the essence of a constant evaluator and a constant interpreter\.

* [constexpr ALL the things\!](https://youtu.be/HMB9oXFobJc), Ben Deane & Jason Turner, C\+\+Now 2017\. A bit outdated but may be interesting\. It's about building a _constexpr_ library\.
* [Compile\-time programming and reflection in C\+\+20 and beyond](https://youtu.be/CRDNPwXDVp0), Louis Dionne, CppCon 2018\. A lot of attention is paid to future reflection in C\+\+\.
* [Useful constexpr](https://youtu.be/MXEgTYDnfJU) by Antony Polukhin \([@antoshkka](https://habr.com/users/antoshkka)\), C\+\+ CoreHard Autumn 2018\. About compilers, reflection and metaclasses\.
* [The clang constexpr interpreter](https://youtu.be/LgrgYD4aibg), Nandor Licker, 2019 LLVM Developers' Meeting\. Rocket science and a code interpreter for _constexpr_\.

And here's also a link to a talk about a killer feature \(in my opinion\) [\[P1040\] std::embed](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1040r3.html), which would work great in tandem with _constexpr_\. But, judging by the [ticket](https://github.com/cplusplus/papers/issues/28), they plan to implement it in C\+\+ _something_\.