100

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

Where and When can I use both of them?

msc
  • 33,420
  • 29
  • 119
  • 214

2 Answers2

124

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::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.

Nihar
  • 553
  • 1
  • 10
  • 29
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • 3
    Note that without `if constexpr` you can have length call a template struct and (partially) specialise that in oder to avoid code duplication. The main benefit of `if constexpr` is thus IMO the more "natural" appearance to the programmer. – Daniel Jour Apr 16 '17 at 07:40
  • ... never mind my previous comment, I see you did indeed include the fact I noted on (_"... and if it is `true` then every other branch (`else if` and `else`) gets discarded."_). – dfrib Jul 31 '17 at 20:34
  • 1
    ```if (std::integral::value) { // is number``` at the beginning should be ```std::is_integral::value``` instead – user3882729 Dec 26 '20 at 23:39
78

The ordinary if statement:

  • Has its condition evaluated every time control reaches it, if ever
  • Determines which of the two substatements to execute, skipping the other
  • Requires both substatements to be well-formed regardless of which one is actually selected at runtime

The if constexpr statement:

  • Has its condition evaluated at compile time once all necessary template arguments have been supplied
  • Determines which of the two substatements to compile, discarding the other
  • Does not require the discarded substatement to be well-formed
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • The arguments don't necessarily have to be *template* arguments though, no? Also, why only two substatements? Can't there be more? – Rakete1111 Apr 16 '17 at 07:08
  • 5
    @Rakete1111 A valid constant expression can only depend on the values of variables that were previously initialized. So once the compiler sees an `if constexpr` statement, it should already have enough information to evaluate the condition. The only exception is when the statement appears in a template, in which case there may not be enough information until the template arguments are specified. A constant expression can't depend on ordinary function arguments, since their values are not known at compile time. – Brian Bi Apr 16 '17 at 07:11
  • 5
    @Rakete1111 An if statement always has either one or two substatements. If you have `if ... else if ... else ...` then you really have two if statements, one nested within the other, each of which has two substatements. – Brian Bi Apr 16 '17 at 07:12
  • 3
    "Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive". Source: https://en.cppreference.com/w/cpp/language/if – Tarquiscani Aug 01 '18 at 16:15
  • This is a little of-topic and heretic, but, why not just `if`? Compiler can decide if it is a `if constexpr` or just `if`. So, this thing does not affect programmers BUT specification must change: if an `if` condition evaluated compile time, only `if` or `else` code compiled. But this is transparent for programmer. Just curious. – Chameleon Mar 10 '20 at 20:44
  • Is it correct to use the term "compile" rather than "instantiate". See [this](https://stackoverflow.com/q/63469333/11205473) – User 10482 Aug 18 '20 at 13:48