c++if-statementc++17constexprif-constexpr

Difference between "if constexpr()" Vs "if()"


What is the difference between if constexpr() and if()?

Where and When can I use both of them?


Solution

  • 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::is_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::is_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 constexpr (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 constexpr (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.