What is the difference between if constexpr()
and if()
?
Where and When can I use both of them?
Constexpr ifIf the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.
A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations. And when a value is computed at compile time instead of run time, it helps your program run faster and use less memory.
In Conclusion. constexpr is an effective tool for ensuring compile-time evaluation of function calls, objects and variables. Compile-time evaluation of expressions often leads to more efficient code and enables the compiler to store the result in the system's ROM.
#define (also called a 'macro') is simply a text substitution that happens during preprocessor phase, before the actual compiler. And it is obviously not typed. constexpr on the other hand, happens during actual parsing. And it is indeed typed.
The only difference is that if constexpr
is evaluated at compile time, whereas if
is not. This means that branches can be rejected at compile time, and thus will never get compiled.
Imagine you have a function, length
, that returns the length of a number, or the length of a type that has a .length()
function. You can't do it in one function, the compiler will complain:
template<typename T> auto length(const T& value) noexcept { if (std::integral<T>::value) { // is number return value; else return value.length(); } int main() noexcept { int a = 5; std::string b = "foo"; std::cout << length(a) << ' ' << length(b) << '\n'; // doesn't compile }
Error message:
main.cpp: In instantiation of 'auto length(const T&) [with T = int]': main.cpp:16:26: required from here main.cpp:9:16: error: request for member 'length' in 'val', which is of non-class type 'const int' return val.length(); ~~~~^~~~~~
That's because when the compiler instantiates length
, the function will look like this:
auto length(const int& value) noexcept { if (std::is_integral<int>::value) { // is number return value; else return value.length(); }
value
is an int
, and as such doesn't have a length
member function, and so the compiler complains. The compiler can't see that statement will never be reached for an int
, but it doesn't matter, as the compiler can't guarantee that.
Now you can either specialize length
, but for a lot of types (like in this case - every number and class with a length
member function), this results in a lot of duplicated code. SFINAE is also a solution, but it requires multiple function definitions, which makes the code a lot longer than it needs to be compared to the below.
Using if constexpr
instead of if
means that the branch (std::is_integral<T>::value
) will get evaluated at compile time, and if it is true
then every other branch (else if
and else
) gets discarded. If it is false
, the next branch is checked (here else
), and if it is true
, discard every other branch, and so on...
template<typename T> auto length(const T& value) noexcept { if constexpr (std::integral<T>::value) { // is number return value; else return value.length(); }
Now, when the compiler will instantiate length
, it will look like this:
int length(const int& value) noexcept { //if (std::is_integral<int>::value) { this branch is taken return value; //else discarded // return value.length(); discarded } std::size_t length(const std::string& value) noexcept { //if (std::is_integral<int>::value) { discarded // return value; discarded //else this branch is taken return value.length(); }
And so those 2 overloads are valid, and the code will compile successfully.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With