Consider this program. The conditional statement will always be true!
#include <iostream>
int main()
{
constexpr double gravity{ 9.8 };
if (gravity == 9.8) // constant expression, always true
std::cout << "Gravity is normal.\\n"; // will always be executed
else
std::cout << "We are not on Earth.\\n"; // will never be executed
}
Thus, we can label the if statement as constexpr.
What is a if constexpr statement?
C++17 introduced if constexpr, and most explanations stop at “the compiler evaluates the condition at compile time.” That description is true but understates the feature. The real significance is that the false branch is not instantiated — it is removed from the program entirely.
The Premise
In the example above, the else true statement is never evaluated however, it is still compiled.
Mark the if as constexpr and the condition is evaluated at compile time. The branch not taken is discarded before code generation:
#include <iostream>
int main()
{
constexpr double gravity{ 9.8 };
if constexpr (gravity == 9.8) // Use of if constexpr
std::cout << "Gravity is normal.\\n";
else
std::cout << "We are not on Earth.\\n";
}
After compilation, the above program is equivalent to:
#include <iostream>
int main()
{
constexpr double gravity{ 9.8 };
std::cout << "Gravity is normal.\\n";
}
Favor constexpr if statements over non-constexpr if statements when the conditional is a constant expression.
Why the discarded branch matters
The crucial property is that the discarded branch is not instantiated.
With a regular if inside a template, both branches must be valid code for whatever type the template is instantiated with. With if constexpr, only the chosen branch needs to be valid. That difference is the entire reason the feature exists.
Consider a generic print function that should call .size() on containers and stream scalars directly:
#include <iostream>
#include <vector>
#include <type_traits>
template <typename T>
void describe(const T& value){
if constexpr (std::is_arithmetic_v<T>){
std::cout << "Scalar value: " << value << '\\n';
} else {
std::cout << "Container of size: " << value.size() << '\\n';
}
}
int main(){
describe(42); // Scalar value: 42
describe(std::vector{ 1, 2, 3 }); // Container of size: 3
}
Replace if constexpr with a regular if and the code stops compiling. When describe is instantiated with int, the compiler must still compile the value.size() branch — and int has no .size() member. The whole template fails.
With if constexpr, the false branch is discarded before semantic checking. The int instantiation never sees value.size() at all, so the program compiles cleanly and dispatches to the correct behaviour for each type.