4

Recently I modify some if constexpr into if in my constexpr functions and found they still work fine and can be evaluated when compile time. Here is a minimum case:

template<int N>
constexpr bool is_negative()
{
    if constexpr  (N >= 0) return false;
    else  return true; 
}
int main()
{
    constexpr  bool v = is_negative<1>();
}

live demo

In the case above, N must be known at compile time because it is non-type template parameter, so if constexpr works fine here. However, it is a constexpr function, so, iirc, it is possible to get a return value even though I replace if constexpr with if:

template<int N>
constexpr bool is_negative()
{
    if  (N >= 0) return false;
    else  return true; 
}
int main()
{
    constexpr  bool v = is_negative<1>();
}

live demo

From cppref, all of the requirements in A constexpr function must satisfy the following requirements: don't mention if. So, IIUC, it should be implementation defined behavior whether constexpr function contain if to be evaluted at compile time even though all related variable is known at compile time(like is_negative above).

So, my conclusion is:

  • Before c++17, we don't have if constexpr, so the choice is if, which means it is not guaranteed to get our constexpr functions get evaluted at compile time, all depend on compiler implementation
  • After c++17, if constexpr is preferred if we want constexpr functions get evaluated at compile time.

All above is my personal thoughts, maybe something important missed/misunderstand, feel free to correct me. The question is still unchanged: if and if constexpr, which should be prefered for constexpr functions which expected to be evaluated at compile time.

references: - What is Allowed in a constexpr Function? - Difference between "if constexpr()" Vs "if()"

user2672165
  • 2,986
  • 19
  • 27
Chen Li
  • 4,824
  • 3
  • 28
  • 55
  • Evaluations of constants have ever been compile-time evaluations. E.g. using template `is_negative()` with `13` makes the condition `13 >= 0` and the compiler will resolve this to 1 (without `constexpr` as well). Modern compilers optimize the whole condition away but I don't know whether this is mandatory and if so, since which version of standard. Btw. `if (N >= 0) return true; else return false;`: why not `return N >= 0;`? Or was it due to simplification for the [mcve]? – Scheff's Cat Dec 17 '18 at 06:37
  • Oops, my bad, it is bad style. I should have posted a better sample... – Chen Li Dec 17 '18 at 06:39
  • OK, no problem. (You could've said as well it was due to MCVE. Such things may look a bit stupid but are intended to just "show the principle". IMHO this is acceptable if stated as such.) ;-) – Scheff's Cat Dec 17 '18 at 06:42
  • @Scheff I found I just tell one op this is aweful style 2 months ago https://stackoverflow.com/a/52826880/6949852 XD – Chen Li Dec 17 '18 at 06:51
  • I'm with @Nelfeal. Arithmetic or relational ops with constants yields constants again (compile-time evaluation). I tried to find the resp. part in [Constant expressions](https://en.cppreference.com/w/cpp/language/constant_expression) but failed until I realized that **Core constant expressions** is expressed "reverse" i.e. they list anything to make an expression _not_ a core constant expression. I needed a bit to realize this... ;-) – Scheff's Cat Dec 17 '18 at 07:02

2 Answers2

12

Before c++17, we don't have if constexpr, so the choice is if, which means it is not guaranteed to get our constexpr functions get evaluted at compile time, all depend on compiler implementation

The fact that an if statement is not constexpr does not mean it can't be evaluated at compile time, as part of a constexpr expression. In your example, v is evaluated at compile time in both cases, because it is required to be: it's a constant expression. That's not implementation defined.

After c++17, if constexpr is prefered if we want constexpr functions get evaluated at compile time.

Constexpr if statements were introduced to solve a problem. Getting constexpr functions to get evaluated at compile time is not that problem.

Here is an example where a constexpr if is required instead of a simple if (taken from cppreference):

template <typename T>
auto get_value(T t) {
    if constexpr(std::is_pointer_v<T>)
        return *t; // deduces return type to int for T = int*
    else
        return t;  // deduces return type to int for T = int
}

Try removing the constexpr keyword and see what happens (demo).

Also, note that you can always solve that problem using other methods, but if constexpr has the advantage of conciseness. For instance, here's an equivalent get_value using tag dispatching:

template<typename T>
auto get_value_impl(T t, std::true_type) {
    return *t;
}
template<typename T>
auto get_value_impl(T t, std::false_type) {
    return t;
}

template<typename T>
auto get_value(T t) {
    return get_value_impl(t, std::is_pointer<T>{});
}

Demo

Nelfeal
  • 12,593
  • 1
  • 20
  • 39
2

The difference between if constexpr and if is whether the expression can always be executed at compile time. In your example, you are using a template argument, so it doesn't really matter which one you write. The difference can be noticed if you would have the following code:

constexpr bool is_negative(int n)
{
    if  (n >= 0) return false;
    else  return true; 
}
int main(int argc, char**)
{
    constexpr  bool v = is_negative(1);
    bool b = is_negative(argc);
    return static_cast<int>(v || b);
}

For the code above, writing if constexpr will not work. As you can call the function with a runtime value.

Again, it shouldn't matter as both code paths are valid. Normal compiler optimizations should kick in when using the function with constant values.

The real interest of if constexpr is when only one path is valid:

template <typename T>
constexpr auto get_value(T t) {
    if constexpr(std::is_pointer_v<T>)
        return *t; // deduces return type to int for T = int*
    else
        return t;  // deduces return type to int for T = int
}

If T would be an int, the code path with *t is invalid as you can't dereference an int. However, because one uses if constexpr instead of if, the code in the false path only needs to be syntactically correct if it depends on the template argument.

As you are searching for a guideline, the compiler already requires: Use if constexpr when one of the code paths is invalid. Use if when depending on arguments.

For the case where the if-condition is calculatable at compile time with 2 valid paths, use if constexpr to require it to be optimized even in debug builds, use if if you want to step through it in debug builds.

If you go to extremes, the expressione could become too complex for the compiler to optimize it in a production build, in which case if constexpr could again be interesting while in the hot path. Personally, I haven't been in this situation yet, however I haven't used it that much.

JVApen
  • 11,008
  • 5
  • 31
  • 67