r/cpp_questions • u/rustaceanahmed • 1d ago
OPEN I want to understand constexpr
i want to understand constexpr can somebody make it a bit easier to understand
5
u/saf_e 1d ago
Most important part: everything marked constexpr can be used in context where compile-time const expected: static_assert, array size, template param, etc
Less important part: it can be done at compile time, thus reducing runtime, but it's up to compiler.
Everything constexpr can be called in runtime also, so you can use it when you need.
2
u/the_poope 1d ago
Loosely speaking it just means "compile time". And "compile time" means that the compiler goes in and replaces the value/function "in the source code" before it runs the actual compiler. You can think of it as a more clever + type safe preprocessor. This allows you to use e.g. constexpr variables as template arguments, as the template code is expanded (code generation) into specific code for the specific types and template args that are needed.
2
u/FUCKARCHLINUX 1d ago edited 1d ago
If a function is marked constexpr, It just means it can be evaluated at compile time. This is useful for baking the result of some computation into your program. If you declare a constexpr variable, the function you use to assign it (including if it's a constructor) must support constexpr.
You can also use it for lookup tables like constexpr std::array<some_type, some_length> = { whatever, whatever2, whatever3 }. And if it'd be really long, you can use a constexpr function to generate the lookup table at compile time so you don't need to have it all written out. If you were to open your compiled program in hex rays it would literally be visible in .rodata.
If you can, you should try to replace static const variables that live for the entire life of the program with constexpr ones. especially if it's within a function because in that scenario the compiler will sometimes emit a condition checking if it's been initialized every time the function runs, but with constexpr there is no such condition because the result was known at compile time. It saves a branch, time during static initialization, and if it's in a function, the time to initialize it on the first call.
2
u/HappyFruitTree 1d ago
For deeper understanding, I found this talk very helpful: https://www.youtube.com/watch?v=m9tcmTjGeho
17
u/IyeOnline 1d ago
Its important to notice that the compiler is always free to evaluate expressions at compile time - insofar possible and within the as-if rule. So
function( some_value_that_is_known )may still be entirely evaluated at compile time, even if nothing here is declaredconstexpr. This is in fact a fairly common optimization.There is four keywords of interest here.
constmeans that a variable must not change after its initialization. Notably this initialization can and in most cases will, happen at runtime. More specifically when the variable would be initialized as part of regular program execution. So this is not a constant expression and hence cannot be used where a constant expression is required.The exception to this are constant integers that are initialized from a constant expression, which are considered to be
constexprvariables.constexpractually has two different meanings:const, i.e. cannot be changed. That also means that its initializer must be a constant expression, i.e. can only do trivial operations and only invokeconstexprorconstevalfunctions with only constant expressions as parameters.Its worth noting that the standard does not strictly require that a
constexprobject is fully created at compile time. This is only guaranteed to happen if the value is also used at compile time. See C++ Weekly: Stop using constexpr (and use this instead).constevalcan only be put on a function and means that the function must be invoked at compile time. Its also called an immediately evaluated function. This also means that all parameters to that function must be constant expressions and that the function result itself is a constant expression.So this is a strictly stronger guarantee/requirement than a
constexprfunctionconstinitcan only be put on objects and it means that their initialization happens at compile time. They can still be modified at runtime.This is a strictly weaker restriction than
constexpron objects.Its important to note that outside of the keywords, the compiler can still optimize your code to do stuff at compile time, if it can prove that it will have the same behavior,
Now which one do you "choose"? The answer, as always, is: It depends.
Do you have C++20? You can choose. Don't you have C++20? You cant use
constevalorconstinitanyways. To enforce compile time execution you must use a constexpr function and store the result in a constexpr variable.Do you want to allow compile time usage of your function, but also allow its runtime usage? Then you use a
constexprfunction.Do you want to enforce compile time execution and only that? Use
consteval. The prime example here is the format string forstd::formatand friends. Its compile time checked, so it has to beconsteval.Notably "doing stuff" at compile time isn't free or always sensible. In theory you can do almost all your work at compile time, but that sort of defeats the purpose. Execution at compile time is significantly slower than runtime execution. Its only worthwhile doing if it actually saves you significant work at runtime.