The question is more academic because even a literal is also eventually stored in memory, at least in the machine code for the instruction it is used in. Still, is there a way to ensure that an identifier will be done away with at compile time and not turn into what is essentially a handicapped variable with memory location and all?
Unfortunately, no. C++ doesn't specify the object format, and therefore, it also doesn't specify what exactly goes into the object file and what doesn't. Implementations are free to pack as much extra stuff into the binary as they want, or even omit things that they determine to not be necessary under the as-if rule.
In fact, we can make a very simple thought experiment to come to a definitive answer. C++ doesn't require there to be a compiler at all. A conformant C++ interpreter is a perfectly valid implementation of the C++ standard. This interpreter could parse your C++ code into an Abstract Syntax Tree and serialize it to disk. To execute it, it loads the AST and evaluates it, one line of C++ code after the other. Your constexpr
variable, #define
, enum
constants, etc all get loaded into memory by necessity. (This isn't even as hypothetical as you might think: It's exactly what happens during constant evaluation at compile time.)
In other words: The C++ standard has no concept of object format or even compilation. Since it doesn't know what compilation even is, it can't specify any details of that process, so there are no rules on what's kept and what's thrown away during compilation.
The C++ Abstract Machine strikes again.
In practice, there are architectures (like ARM) that don't have instructions to load arbitrary immediates into registers, which means that even a plain old integer literal like 1283572434
will go into a dedicated constant variable section in memory, which you can take the address of. The same can and will happen with constexpr
variables, enums, and even #define
.
Compilers for x86-64 do this as well for constants that are too large to load via regular mov reg, imm
instructions. Very large 256-bit or even 512-bit constants are generally loaded into vector registers by loading them from a constant section somewhere in memory.
Most compilers are of course smart enough to optimize away constants that are only used at compile time. It's not guaranteed by the standard, though, and not even by the compilers themselves.
Here's an example where GCC places a #define
-d constant into a variable and loads it from memory when needed (Godbolt):
#include <immintrin.h>
#define THAT_VERY_LARGE_VALUE __m256i{1111, 2222, 3333, 4444}
__m256i getThatValue() {
return THAT_VERY_LARGE_VALUE;
}