I'm not convinced by the other answers' explanations.
Here is GCC Team member Jakub Jelink talking about their constexpr cache in a bug ticket. He describes that they're caching constexpr results depending on arguments (among other things).
Relevant code in source mirror. This is a little more involved. According to how the hash table is handled in the code, GCC globally caches the results (per compilation units) of constexpr calls according to constant argument values, template parameters, and object instance (if it's actually a method that can be evaluated on a constexpr object). Although the code dealing with this is quite verbose.
In addition, here's a test supporting the memoization claim by HolyBlackCat:
#include <stdio.h>
static constexpr unsigned long long test(int arg)
{
unsigned long long dummy = 0;
for (unsigned long long i = 0; i < 1000000ULL + arg; ++i)
{
dummy += 1;
}
return dummy;
}
int main(int argc, char **argv)
{
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(5); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(6 /*!*/); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(7 /*!*/); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(8 /*!*/); printf("Result: %llu\n", result); }
{ constexpr unsigned long long result = test(9 /*!*/); printf("Result: %llu\n", result); }
return 0;
}
The idea is to run some constexpr function that runs in some known amount of real time. On my machine, the function took like 2 seconds. The exact time is not important -- what's important is that the execution time is in fact proportional to the number of loop iterations.
The following observations hint at memoization:
- If only one of the
result lines in main is present, then the compile time is about some value x.
- If ALL of the
result lines with argument 5 in test(5) are present (but none of the lines with other arguments), the compile time is the same value x.
- If the the
result lines with arguments 6, 7, 8, 9 are present as well, then the compile time is about 5 times x.
Also keep in mind that, while the compiler is perfectly optimize the loop to a simple addition in the generated code when compiling with -O2, it completely fails to do any sort of optimization during constexpr evaluation, as is evident by the absurdly long compile times. If it were to do any nontrivial optimizations at that stage, it would surely be able to collapse the loop. Guesswork: Having contributed code to compilers before I would consider it plausible that the pipeline architecture is actually way too complicated for full optimization on constexpr code that gets evaluated at compile time, and that it may actually be evaluating an expression tree which is very close to the original AST in structure.
Tested with g++ -std=c++14
gcc version 5.4.0.20160609 running on linux mint.