Difference between revisions of "Constexpr"
m (→Basics) |
|||
Line 41: | Line 41: | ||
constexpr int y = 69; // Compiles fine | constexpr int y = 69; // Compiles fine | ||
</source> | </source> | ||
+ | |||
+ | == Advanced == | ||
+ | === Pointers === | ||
+ | <code>constexpr</code> Pointers are implicitly const, i.e you cannot change where the pointer points to but you can change the data at the memory address. | ||
+ | <source> | ||
+ | static constexpr int x = 5 | ||
+ | static constexpr int y = 10; | ||
+ | constexpr int* integer = &x; // Equivalent to 'int* const integer = &x' but evaluated at compile time | ||
+ | |||
+ | integer = &y // Error, cannot retarget the pointer | ||
+ | *integer = 15 // Valid, value of x is changed to 15 | ||
+ | </source> | ||
+ | |||
+ | To ensure the pointer is evaluated at compile time, cannot be retargeted and is not able to change its target a <code>const</code> keyword can be added after <code>constexpr</code> | ||
+ | <source> | ||
+ | static constexpr int x = 5; | ||
+ | static constexpr int y = 10; | ||
+ | constexpr const int* integer = &x; // Equivalent to 'const int* const integer = &x' but guaranteed to be evaluated at compile time. | ||
+ | |||
+ | integer = &y; // Error, Cannot Retarget the pointer | ||
+ | *integer = 15; // Error, the value at the pointed address cannot be modified. | ||
+ | </source> | ||
+ | |||
+ | === Functions === | ||
+ | Functions can also be evaluated at compile time by using the keyword <code>constexpr</code> which means "should be usable in a constant expression when given constant expressions as arguments." | ||
+ | <source> | ||
+ | constexpr double square(double x) {return x * x;} | ||
+ | </source> | ||
+ | |||
+ | If the arguments to a function is a constexpr the function returns a constexpr. | ||
+ | |||
+ | To declare a function as a constexpr, the function cannot contain: | ||
+ | |||
+ | # ASM definition {<code>movl $7, %eax</code>} | ||
+ | # A <code>goto</code> statement | ||
+ | # A statement with a label other than case and default {<code>label: take that compiler</code>} | ||
+ | # A try-block {<code>try {std::cout << "My dick stinks" << std:endl; }</code>} | ||
+ | # Variable definition of non-literal type {<code>MyCustomClass anObject</code>} | ||
+ | # Static variable | ||
+ | # Uninitialized variable | ||
+ | |||
+ | Why do you care? Constexpr function are a good way to write cleaner code as compared to using macros everywhere, improve performance (but rarely used for this purpose) | ||
+ | |||
+ | === Constexpr Classes === | ||
+ | |||
+ | Simple user-defined classes can also be used as constant expressions. | ||
+ | |||
+ | To make a class a <code>constexpr</code> class it needs to have a <code>constexpr</code> constructor with an empty body and all member functions should be initialized by constant expressions. | ||
+ | |||
+ | Declaring a constructor as <code>constexpr</code> turns your class into "User Defined Literals" i.e. you class can now be used as a literal | ||
+ | |||
== Planet Chili Tutorial Video == | == Planet Chili Tutorial Video == |
Latest revision as of 20:06, 12 November 2018
constexpr
is a keyword applied to variables and functions. It's basically the same as the [const]
keyword but enforces an additional rule that "The value of this const will be available at compile time"
Contents
Difference between const & constexpr
Basics
In C++ there can be 2 types of constants:
Whose value is known at compile time.
const int x = 50;
const int z = x + 1;
During compile time, the compiler knows the value of x and z would be 50 and 51 respectively. And the values won’t change throughout the lifetime of the program. So it goes “I’ll just substitute all the references to x and z with 50 and 51, instead of looking up what the values of x and z every time during runtime”.
This allows 50 and 51 to be used as literals (hardcoded values) with all their advantages and at the same time not clutter our code with random numbers that are hard to interpret the meaning of.
Whose value is not known at compile time.
const int y = GenRandomNumber();
In this case the value of y would be impossible to determine at compile time as a we don’t know what random number would be generated at runtime.
NOTE: the variable is still a constant, once a random number is assigned to it the value cannot change.
When the compiler comes across scenario #1 it marks x an z as constant expressions i.e. their value can be evaluated at compile time and in scenario #2 marks y as being a constant but not a constant expression as it can only be evaluated during runtime.
This can be a bit confusing in certain contexts e.g.
int anArray[x]; // Compiles successfully
int anotherArray[y]; // Fails compilation
Certain operations require compile-time knowledge of a variable, in this case, anotherArray declaration would throw an error even though y is a const just like x. This is because the value of y cannot be computed until the function GenRandomNum() is run.
Due to this confusion, it is hard to evaluate what constants will be available during compile time, and which won’t like in our example above. As your code base gets bigger this can spiral out of control, as multiple people are working on the same code base.
This is where the keyword constexpr
comes in as it tells the compiler that the value of this variable should be deduced at compile time to allow for scenarios such as the one above. If y was declared as constexpr int y = GetRandomNumber()
the compiler would throw an error that the value cannot be evaluated at compile time. It clarifies the intended use of the variable.
constexpr int y = GetRandomNumber(); // error
constexpr int y = 69; // Compiles fine
Advanced
Pointers
constexpr
Pointers are implicitly const, i.e you cannot change where the pointer points to but you can change the data at the memory address.
static constexpr int x = 5
static constexpr int y = 10;
constexpr int* integer = &x; // Equivalent to 'int* const integer = &x' but evaluated at compile time
integer = &y // Error, cannot retarget the pointer
*integer = 15 // Valid, value of x is changed to 15
To ensure the pointer is evaluated at compile time, cannot be retargeted and is not able to change its target a const
keyword can be added after constexpr
static constexpr int x = 5;
static constexpr int y = 10;
constexpr const int* integer = &x; // Equivalent to 'const int* const integer = &x' but guaranteed to be evaluated at compile time.
integer = &y; // Error, Cannot Retarget the pointer
*integer = 15; // Error, the value at the pointed address cannot be modified.
Functions
Functions can also be evaluated at compile time by using the keyword constexpr
which means "should be usable in a constant expression when given constant expressions as arguments."
constexpr double square(double x) {return x * x;}
If the arguments to a function is a constexpr the function returns a constexpr.
To declare a function as a constexpr, the function cannot contain:
- ASM definition {
movl $7, %eax
} - A
goto
statement - A statement with a label other than case and default {
label: take that compiler
} - A try-block {
try {std::cout << "My dick stinks" << std:endl; }
} - Variable definition of non-literal type {
MyCustomClass anObject
} - Static variable
- Uninitialized variable
Why do you care? Constexpr function are a good way to write cleaner code as compared to using macros everywhere, improve performance (but rarely used for this purpose)
Constexpr Classes
Simple user-defined classes can also be used as constant expressions.
To make a class a constexpr
class it needs to have a constexpr
constructor with an empty body and all member functions should be initialized by constant expressions.
Declaring a constructor as constexpr
turns your class into "User Defined Literals" i.e. you class can now be used as a literal