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.
The only difference is that
if constexpr
is evaluated at compile time, whereasif
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:Error message:
That's because when the compiler instantiates
length
, the function will look like this:value
is anint
, and as such doesn't have alength
member function, and so the compiler complains. The compiler can't see that statement will never be reached for anint
, 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 alength
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 ofif
means that the branch (std::is_integral<T>::value
) will get evaluated at compile time, and if it istrue
then every other branch (else if
andelse
) gets discarded. If it isfalse
, the next branch is checked (hereelse
), and if it istrue
, discard every other branch, and so on...Now, when the compiler will instantiate
length
, it will look like this:And so those 2 overloads are valid, and the code will compile successfully.
The ordinary if statement:
The if constexpr statement: